📄 lockfile.java
字号:
/* Copyright (c) 2001-2005, The HSQL Development Group
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* Neither the name of the HSQL Development Group nor the names of its
* contributors may be used to endorse or promote products derived from this
* software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL HSQL DEVELOPMENT GROUP, HSQLDB.ORG,
* OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package org.hsqldb.persist;import java.io.DataInputStream;import java.io.File;import java.io.FileInputStream;import java.io.RandomAccessFile;import org.hsqldb.DatabaseManager;import org.hsqldb.HsqlDateTime;import org.hsqldb.HsqlException;import org.hsqldb.Trace;import org.hsqldb.lib.FileUtil;import org.hsqldb.lib.HsqlTimer;import org.hsqldb.lib.java.JavaSystem;/** * The base HSQLDB cooperative file locking implementation and factory. <p> * * <hr> * * Here is the way this class operates: <p> * * <ol> * <li>A file with a well-known path relative to each database instance * is used to implement cooperative locking of database files across * process boundaries (database instances running in different JVM * host processes) and class loader contexts (databases whose classes * have been loaded by distinct class loaders such that their open * database repositories are distinct and are inaccessible across * the class loader context boundaries).<p> * * <li>A background thread periodically writes a timestamp to this object's * lock file at {@link #HEARTBEAT_INTERVAL} millisecond intervals, * acting as a heartbeat to indicate that a lock is still held.<p> * * <li>The generic lock attempt rules are: <p> * <ul> * <li>If a lock condition is already held by this object, do nothing and * signify that the lock attempt was successful, else...<p> * * <li>If no lock file exists, go ahead and create one, silently issue the * {@link java.io.File#deleteOnExit File.deleteOnExit()} directive via * refelective method invocation (in order to stay JDK 1.1 compliant), * schedule a periodic heartbeat task and signify that the lock attempt * was successful, else...<p> * * <li>The lock file must already exist, so try to read its heartbeat * timestamp. If the read fails, assume that a lock condition is held by * another process or a database in an inaccessible class loader context * and signify that the attempt failed, else if the read value * is less than <code>HEARTBEAT_INTERVAL</code> milliseconds into the * past or futuer, assume that a lock condition is held by another * process or a database in an inaccessible class loader context and * signify that the lock attempt failed, else assume that the file is * not in use, schedule a periodic heartbeat task and signify that the * lock attempt was successful.<p> * * </ul> * <li>The generic release attempt rules are:<p> * <ul> * <li>If a lock condition is not currently held, do nothing and signify * that the release attempt was successful, else...<p> * * <li>A lock condition is currently held, so try to release it. By * default, releasing the lock condition consists of closing and * nullifying any objects that have a file descriptor open on the * lock file. If the release is successful, cancel the periodic * heartbeat task and signify that the release succeeded, else signify * that the release attempt failed.<p> * </ul> * </ol> <p> * * In addition to the generic lock and release rules, the protected methods * {@link #lockImpl() lockImpl()} and {@link #releaseImpl() releaseImpl()} * are called during lock and release attempts, respectively. This allows * transparent, JDK 1.1 compliant integration of extended strategies for * locking and releasing, based on subclassing and reflective construction * of such specializations in the factory method * {@link #newLockFile newLockFile()}, determined by information gathered * at run-time. <p> * * In particular, if it is available at runtime, then newLockFile() retrieves * instances of {@link org.hsqldb.NIOLockFile NIOLockFile} to capitalize, * when possible, on the existence of the {@link java.nio.channels.FileLock * FileLock} class. If the <code>NIOLockFile</code> class does not exist at * run-time or the java.nio classes it uses are not supported under the * run-time JVM, then newLockFile() produces vanilla LockFile instances, * meaning that only purely cooperative locking takes place, as opposed to * possibly O/S-enforced file locking which, at least in theory, is made * available through the {@link java.nio.channels} package). However, it * must be noted that even if a JVM implementation provides the full * java.nio.channels package, it is not absolutely required to guarantee * that the underlying platform (the current operating system) provides * true process-wide file locking. <p> * * <b>Note:</b> <p> * * The <code>NIOLockFile</code> descendent exists because it has been determined * though experimenatation that <code>java.nio.channels.FileLock</code> * does not always exhibit the correct/desired behaviour under reflective * method invocation. That is, it has been discovered that under some operating * system/JVM combinations, after calling <code>FileLock.release()</code> * via a reflective method invocation, the lock is not released properly, * deletion of the lock file is not possible even from the owning object * (this) and it is impossible for other <code>LockFile</code> instances * or any other objects or processes to successfully obtain a lock * condition on the lock file, despite the fact that the <code>FileLock</code> * object reports that its lock is invalid (was released successfully). * Frustratingly, this condition appears to persist until full exit of the * JVM process in which the <code>FileLock.tryLock()</code> method was * reflectively invoked. <p> * * To solve this, the original <code>LockFile</code> class was split in two and * instead of reflective method invocation, reflection-based class * instantiation is now performed at the level of the <code>newLockFile()</code> * factory method. Similarly, the HSQLDB ANT build script detects the presence * or abscence of JDK 1.4 features such as java.nio and only attempts to build * and deploy <code>NIOLockFile</code> to the hsqldb.jar if such features are * reported present. </p> * * @author boucherb@users * @version 1.7.2 * @since 1.7.2 */public class LockFile { /** Canonical reference to this object's lock file. */ protected File f; /** Cached value of the lock file's canonical path. */ private String cpath = null; /** * A RandomAccessFile constructed from this object's reference, f, to its * lock file. <p> * * This RandomAccessFile is used to periodically write out the heartbeat * timestamp to this object's lock file. */ protected RandomAccessFile raf; /** * The period, in milliseconds, at which heartbeat timestamps are written * to this object's lock file. */ public static final long HEARTBEAT_INTERVAL = 10000; /** * A magic value to place at the beginning of the lock file to * differentiate it from other files. The value is "HSQLLOCK".getBytes(). */ public static final byte[] MAGIC = "HSQLLOCK".getBytes(); /** Indicates whether this object has a lock condition on its lock file. */ protected boolean locked; /** * The timed scheduler with which to register this object's * heartbeat task. */ protected static final HsqlTimer timer = DatabaseManager.getTimer(); /** * An opaque reference to this object's heatbeat task. */ private Object timerTask; /** * Attempts to read the hearbeat timestamp from this object's lock file * and compare it with the current time. <p> * * An exception is thrown if it must be presumned that another process has * locked the file, using the following rules: <p> * * <ol> * <li>If the file does not exist, this method returns immediately. * * <li>If an exception is raised reading the file, then an exeption is * thrown. * * <li>If the read is successful and the timestamp read in is less than * <code>HEARTBEAT_INTERVAL</code> milliseconds into the past or * future, then an exception is thrown. * * <li>If no exception is thrown in 2.) or 3.), this method simply returns. * </ol> * @throws Exception if it must be presumed that another process * or isolated class loader context currently has a * lock condition on this object's lock file */ private void checkHeartbeat() throws Exception { long lastHeartbeat; String mn; String path; mn = "checkHeartbeat(): "; path = "lock file [" + cpath + "]"; trace(mn + "entered."); if (!f.exists()) { trace(mn + path + " does not exist. Check OK."); return; } if (f.length() != 16) { trace(mn + path + " length != 16; Check OK."); return; } try { lastHeartbeat = System.currentTimeMillis() - readHeartbeat(); } catch (Exception e) { // e.printStackTrace(); throw new Exception( Trace.getMessage( Trace.LockFile_checkHeartbeat, true, new Object[] { e.getMessage(), cpath })); } trace(mn + path + " last heartbeat " + lastHeartbeat + " ms ago."); if (Math.abs(lastHeartbeat) < HEARTBEAT_INTERVAL) { throw new Exception( Trace.getMessage( Trace.LockFile_checkHeartbeat2, true, new Object[] { mn, path })); } } /** * Closes this object's {@link #raf RandomAccessFile}. * * @throws Exception if an IOException occurs */ private void closeRAF() throws Exception { String mn; mn = "closeRAF(): "; trace(mn + "entered."); if (raf == null) { trace(mn + "raf was null upon entry. Exiting immediately."); } else { trace(mn + "closing " + raf); raf.close(); trace(mn + raf + " closed successfully. Setting raf null"); raf = null; } } /** * Initializes this object with the specified <code>File</code> * object. <p> * * The file argument is a reference to this object's lock file. <p> * * This action has the effect of attempting to release any existing * lock condition and reinitializing all lock-related member attributes * @param file a reference to the file this object is to use as its * lock file */ private void setFile(File file) throws Exception { if (isLocked()) { try { tryRelease(); } catch (Exception e) { trace(e); } } f = FileUtil.canonicalFile(file); cpath = f.getPath(); raf = null; locked = false; } /** * Provides any specialized locking actions for the * {@link #tryLock() tryLock()} method. <p> * * Descendents are free to provide additional functionality here, * using the following rules: * * <pre>
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -