LockFile
factory.
The need is obvious for inter-process cases, but it is no less essential for in-process Java clients whose classes have been loaded by isolated class loaders. This is because static fields--the conventional * means for supporting in-process global discovery--become distinct and inaccessible across Java class loader context boundaries when the contexts do not share a common parent class loader or do not implement normal parent class loader delegation semantics.
* The only purely in-process global discovery alternative known to the author is to reflect upon objects found while traversing up the Java runtime thread hierarchy. However, this method is often subject to Java security restrictions whose collective purpose is essentially dissimilar to that of restrictions in effect relative to cooperative file locking requirements, making it a generally unacceptable option in this context.
This can occur only under pre-JDK 1.2 runtimes; or when the
underlying platform does not correctly support java.; or when the underlying file is
deleted within a very short time after i.), above (typically
on the order of microseconds).
If the underlying platform employs a kernel-enforced mandatory
file locking blanket policy for open files (e.g. Windows
tm), then this is likely a non-issue. And if
this case makes possible a race condition with another
LockFile object (because the test for existence and
subsequent file creation is not atomic relative to all other
file system actions), it is still very unlikely that
so unfortunate a timing will occur as to allow simultaneous
lock conditions to be established. Finally, if some
non-LockFile entity deleted the file, then there are
much worse things to worry about, in particular that the files
this object is supposed to protect are in reality subject to
arbitrary external modification and deletion.
MAGIC and heartbeat
timestamp values, fast failing the current poll if the stream
cannot be opened.
MAGIC value, failing the current poll
if the value cannot be read or it is not the expected
value.
MAGIC value (was
not an HSQLDB lock file).
LockFile.
In this case, signify failure indicating the last encountered reason, else...
By default, releasing the lock condition consists of closing and nullifying any objects that have a file descriptor open on the lock file, cancelling the periodic heartbeat timestamp writer task and deleting the lock file. If the release occurs without raising an exception, signify success, else signify that the release attempt might have failed.
doOptionalLockActions() and
doOptionalReleaseActions() are invoked
during lock and release attempts, respectively. This enables integration of
extended lock and release strategies based on subclassing. Subclass
availability is automatically detected and exposed by the factory method
newLockFile().
In particular, if USE_NIO_FILELOCK_PROPERTY is true and the required
classes are available at static initialization, then newLockFile()
produces org.hsqldb.persist.NIOLockFile instances.
When NIOLockFile instances are produced, then it is possible that
true kernel-enforced advisory or mandatory file locking is used to protect
the underlying lock file from inadvertent modification (and possibly even
from deletion, including deletion by the system superuser).
Otherwise, newLockFile() produces vanilla LockFile
instances, which exhibit just the elementary cooperative locking behavior on
platforms that do not, by default, implement kernel-enforced mandatory
locking for open files.
At this point, it must be noted that not every target platform upon which Java can run actually provides true kernel-enforced mandatory (or even advisory) file locking. Indeed, even when a target platform does provide such locking guarantees for local file systems, it may not be able to do so for network file systems, or it may only be able to do so safely (or at all) with certain restrictions. Further, external system configuration may be a prerequisite to enable mandatory locking on systems that support it but employ advisory locking by default.
In recognition of these facts, the official Java NIO package specification explicitly states basically the same information. What is unfortunate, however, is that no capabilities API is yet provided as part of the package. What is even more unfortunate is that without something like a capabilities API, it is impossible for an implementation to indicate or clients to distinguish between simple lack of platform support and cases involving immature Java runtimes that do not fully or correctly implement all NIO features (and hence may throw exceptions at unexpected times or in places where the API specification indicates none can be thrown).
It is for the preceding reasons that, as of HSQLDB 1.8.0.3,
FileLock's use of Java NIO has been made a purely optional feature.
Previous to HSQLDB 1.8.0.3, if NIO was detected available, used to create a
FileLock and failed, then the enclosing cooperative lock attempt
failed also, despite the fact that a vanilla locking approach could
succeed.
Polling Configuration:
Although the HEARTBEAT_INTERVAL and default polling values may
seem quite conservative, they are the result of ongoing research into
generally reasonable concerns regarding normal timing and resource
availability fluctuations experienced frequently under most, if not all
operating systems.
Regardless, flexibility is almost always a good thing, so this class is designed to allow polling interval and retry count values to be configured at run-time.
At present, this can be done at any time by setting the system properties
whose names are POLL_RETRIES_PROPERTY and POLL_INTERVAL_PROPERTY.
Some consideration has also been given to modifying the polling scheme so that run-time configuration of the HEARTBEAT_INTERVAL is possible. For now, however, this option has been rejected due to the relative complexity of guaranteeing acceptably safe, deterministic behaviour. On the other hand, if it can be guaranteed that certain site invariants hold (in particular, that only one version of the hsqldb jar will ever be used to open database instances at the site) and it is desirable or required to experiment with a lower interval, then it is recommended for now simply to recompile the jar using a different value in the static field assignment. Note that great care should be taken to avoid assigning too low a value, or else it may become possible that even very short-lived timing and resource availability fluctuations will cause incorrect operation of this class.
NIO Configuration:
Starting with 1.8.0.3, NIO-enhanced file lock attempts are turned off by
default. The general reasons for this are discussed above. Anyone interested
in the reading the detailed research notes should refer to the overview of
NIOLockFile. If, after reviewing the notes and the capabilities of
the intended target platform, one should still wish to enable NIO-enhanced
file lock attempts, it can be done by setting the system property USE_NIO_FILELOCK_PROPERTY true at JVM startup (for example, by using a
command-line -D<property-name>=true directive). Be aware that
the system property value is read only once, in the static initializer block
for this class.
Design Notes:
First, it should be noted that no thread synchronization occurs in
this class. Primarily, this is because the standard entry point,
newLockFileLock(String), is always called from within a block
synchronized upon an HSQLDB Database instance. If this class is to be used
elsewhere and it could be accessed concurrently, then access should be
synchronized on an appropriate monitor. That said, certain members of this
class have been declared volatile to minimize possibility of inconsistent
views under concurrent read-only access.
Second, to the limit of the author's present understanding, the implementation details of this class represent a good compromise under varying and generally uncontrollable JVM, OS and hardware platform limitations/capabilities, as well as under usability considerations and external security or operating constraints that may need to be imposed.
Alternate approaches that have been considered and rejected for now include:
As a final note and sign post for developers starting to work with Java NIO:
A separate NIOLockFile descendant exists specifically
because it was determined through experimentation that
java.nio.channels.FileLock does not always exhibit the correct
or desired behaviour under reflective method invocation. That is, it was
discovered that under some operating system/JVM combinations, after calling
FileLock.release() 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 LockFile
instances, other in-process objects or other processes to successfully obtain
a lock condition on the lock file, despite the fact that the
FileLock object reports that its lock is invalid (was released
successfully). Frustratingly, this condition appears to persist until full
exit of the process hosting the JVM in which the FileLock.tryLock()
method was reflectively invoked.
To solve this, the original LockFile class was split in two and
instead of reflective method invocation, subclass instantiation is now
performed at the level of the newLockFile() factory method.
Similarly, the HSQLDB ANT build script now detects the presence or absence
of JDK 1.4+ features such as java.nio and only attempts to build and deploy
NIOLockFile to the hsqldb.jar if such features are reported
present.
The nio lock file was removed in version 2.0 and reference removed in 2.5.1.
| Modifier and Type | Class and Description |
|---|---|
| public abstract static class | LockFile.
Base exception class for lock condition specific exceptions. |
| public static class | LockFile.
Thrown when canonicalization of a |
| public static class | LockFile.
Thrown when access to a |
| private class | LockFile.
For internal use only. |
| public static class | LockFile.
Thrown when an externally held lock condition prevents lock acquisition. |
| public static class | LockFile.
Thrown when access to a |
| public static class | LockFile.
Thrown when access to a |
| public static class | LockFile.
Thrown when access to a |
| public static class | LockFile.
Thrown when it is detected that a LockFile object's target file does not have the expected length. |
| public static class | LockFile.
Thrown when it is detected that a LockFile object's target file does not start with the expected MAGIC value. |
| Modifier and Type | Field and Description |
|---|---|
| private String | cpath
Cached value of the lock file's canonical path Design Note:
Should really be final, but finality makes reflective construction
and adherence to desirable |
| protected File | file
Canonical reference to this object's lock file. |
| public static final long | HEARTBEAT_INTERVAL
Arbitrary period, in milliseconds, at which heartbeat timestamps are written to this object's lock file. |
| public static final long | |
| protected volatile boolean | locked
Indicates presence or absence of the cooperative lock condition. |
| protected static final byte[] | MAGIC
Value written at the beginning of an HSQLDB lock file to distinguish it from other file types. |
| public static final boolean | NIO_FILELOCK_AVAILABLE
Statically computed indication of |
| public static final Class | NIO_LOCKFILE_CLASS
Statically computed reference to the |
| public static final String | POLL_INTERVAL_PROPERTY
System property that can be used to override the default number of milliseconds between each heartbeat poll retry. |
| public static final int | POLL_RETRIES_DEFAULT
Number of retries used by default in |
| public static final String | POLL_RETRIES_PROPERTY
System property that can be used to override the default number of heartbeat poll retries. |
| protected volatile RandomAccessFile | raf
A |
| protected static final HsqlTimer | timer
The timed scheduler with which to register this object's heartbeat task. |
| private volatile Object | timerTask
Opaque reference to this object's heartbeat task. |
| public static final boolean | USE_NIO_FILELOCK_DEFAULT
Whether |
| public static final String | USE_NIO_FILELOCK_PROPERTY
System property that can be used to control whether nio file locking is attempted. |
| public static final int | USED_REGION
Size, in bytes, of the region at the beginning of a lock file that is actually used to record lock information. |
| Access | Constructor and Description |
|---|---|
| protected |
| Modifier and Type | Method and Description |
|---|---|
| private void | checkHeartbeat(boolean
if withCreateNewFile)true, attempt to employ
File.createNewFile() as part of the check so as to
eliminate potential race conditions when establishing a new
lock fileChecks whether the underlying file is an HSQLDB lock file and, if so,
whether its heartbeat timestamp is live (is, as far as can be known,
presumably in use by another |
| private void | checkMagic(final DataInputStream
the stream to check dis)Checks whether the given |
| private void | |
| protected boolean | Returns: true if optional lock actions are performed and they
succeed, else falseProvides any optional locking actions for the |
| protected boolean | Returns: true if optional release actions are performed and they
succeed, else falseProvides any optional release actions for the |
| public final boolean | Returns: true if this object is equal to the obj
argument; false otherwise.the reference object with which to compare. obj)Overrides java. Tests whether some other object is "equal to" this one. |
| public final String | Returns: the canonical path of this object's lock file.Retrieves the canonical path of this object's lock file, as a
|
| public long | Returns: the interval, in milliseconds, thatpollHeartbeat
waits between failed invocations of checkHeartbeatRetrieves the interval, in milliseconds, that |
| public int | Returns: the number of timescheckHeartbeat may fail before
pollHeartbeat fails as a consequence.Retrieves the number of times |
| public final int | Returns: a hash code value for this object.Overrides java. Retrieves the hash code value for this object. |
| public final boolean | Returns: true if this object has successfully obtained and is
still holding (has not yet released) a lock condition, else
falseRetrieves whether this object has successfully obtained and is still holding (has not yet released) a cooperative lock condition on its lock file. |
| public static boolean | Returns: true if there is currently something preventing the
acquisition of a cooperative lock condition using the specified
path, else falsethe path to test path)Retrieves whether there is potentially already a cooperative lock, operating system lock or some other situation preventing a cooperative lock condition from being acquired using the specified path. |
| public boolean | Returns: true if this object holds a valid lock condition on its
lock file; else falseRetrieves whether this object holds a valid lock condition on its lock file. |
| public static LockFile | Returns: aLockFile instance initialized with a File
object whose path is the one specified by the given path
argument.the path of the path)File object with which the retrieved
LockFile object is to be initializedRetrieves a |
| public static LockFile | Returns: a newLockFile object holding a cooperative lock
condition upon the file with the given path, appended with the
extension '.lck'of the lock file, to which will be appended '.lck' path)
|
| private static LockFile | Returns: a newNIOLockFile, or null if not available
under the current runtime environmentRetrieves a new |
| private void | |
| private void | |
| private long | Returns: the hearbeat timestamp read from this object's lock file, as along value or, if this object's lock
file does not exist, Long.MIN_VALUE, the earliest time
representable as a long in Java.Retrieves the last written hearbeat timestamp from this object's lock file. |
| private void | |
| private void | |
| private void | |
| public String | Returns: a String representation of this object.Overrides java. Retrieves a String representation of this object. |
| protected String | Returns: an implementation-specific tail value for thetoString()
methodRetrieves an implementation-specific tail value for the
|
| public final boolean | Returns: true if and only if a lock condition is obtained
successfully;
false otherwise. In general, an exception will
always be thrown if a lock condition cannot be obtained for
any reasonAttempts to obtain a cooperative lock condition upon this object's lock file. |
| public final boolean | Returns: true if this object does not currently hold a
lock condition or the lock is released completely (including
successful file deletion), else false.Attempts to release any cooperative lock condition this object may hold upon its lock file. |
| private void | |
| private void | writeMagic()
Writes the |