Class LockFile
- java.lang.Object
-
- org.hsqldb.persist.LockFile
-
public class LockFile extends java.lang.ObjectBase cooperative file locking implementation and LockFile factory.
Provides a facility for cooperative file locking across process boundaries and isolated in-process class loader contexts.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.
Here is the way this class presently operates:- A file with a commonly agreed-upon path is used to implement
cooperative locking semantics regarding another set of files with
commonly agreed-upon paths.
- In particular, a background thread periodically writes a timestamp
value, which acts as a heartbeat that indicates to others whether a
cooperative lock condition is currently held.
- In addition, a magic value is written so that it is possible to
distinguish with a reasonably high degree of accuracy between the
existence of a lock file and some other type of file.
- The generic rules used to aquire a cooperative lock condition are
as follows:
- If a lock condition is already held by this object, do nothing and
signify that the lock attempt was successful, else...
- Poll the underlying file, using a configured maximum number of
retries and a configured interval between the end of a failed
poll and the beginning of the next.
- For each poll:
- Attempt to atomically create the underlying file if and only
if it does not yet exist, exit the polling loop immediately
indicating success if the attempt succeeds, else fast fail
the current poll if a security exeption is thrown in response
to the attempt, else...
- Test if the underlying file exists, fast failing the current
poll if it is impossible to determine (i.e. if a security
exeption is thrown).
- If the file does not exist, exit the polling loop immediately
indicating success.
This can occur only under pre-JDK 1.2 runtimes; or when the underlying platform does not correctly support
File.createNewFile(); 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.
- Test the file's length, fast failing the current poll if the
length cannot be determined or it is not the expected
value.
- Open a stream to read the file's MAGIC and heartbeat
timestamp values, fast failing the current poll if the stream
cannot be opened.
- Test the file's MAGIC value, failing the current poll
if the value cannot be read or it is not the expected
value.
- Test the file's heartbeat timestamp value, fast failing the
current poll if it cannot be read or it is less than a
commonly agreed-upon value into the past (or future, to
overcome a caveat observed by a patch contributor).
- Attempt to atomically create the underlying file if and only
if it does not yet exist, exit the polling loop immediately
indicating success if the attempt succeeds, else fast fail
the current poll if a security exeption is thrown in response
to the attempt, else...
- If the polling phase exits with a failure indication, then one or
more of the following cases must have been true at every poll
iteration:
- The file had the wrong length or MAGIC value (was not an HSQLDB lock file).
- The file was deleted externally after a poll's initial test for existence and recreated at some point before the next poll's initial test for existence.
- An incompatible OS-enforced security restriction was in effect.
- An incompatible Java-enforced security restriction was in effect.
- The target file system media was effectively inaccessible.
- A cooperative lock condition was held by some other LockFile.
- A kernel-enforced manditory or advisory file lock was held.
In this case, signify failure indicating the last encountered reason, else...
- Open the file for reading and writing, write the magic value and
an initial heartbeat timestamp, schedule a periodic heartbeat
timestamp writer task and signify success.
- If a lock condition is already held by this object, do nothing and
signify that the lock attempt was successful, else...
- The generic rules used to release a cooperative lock condition are:
- If a lock condition is not currently held, do nothing and signify
success, else...
- A lock condition is currently held by this object, 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, 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.
- If a lock condition is not currently held, do nothing and signify
success, else...
Additionally,doOptionalLockActions()anddoOptionalReleaseActions()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 methodnewLockFile().In particular, if
USE_NIO_FILELOCK_PROPERTYis true and the required classes are available at static initialization, then newLockFile() producesNIOLockFileinstances.When NIOLockFile instances are produced, then it is possible that true kernel-enforced advisory or manditory 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 manditory 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 manditory (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 manditory 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 distiguish 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_INTERVALand 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_PROPERTYandPOLL_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 propertyUSE_NIO_FILELOCK_PROPERTYtrue 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 comprimse under varying and generally uncontrollable JVM, OS and hardware platform limitations/capabilites, 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:
- Socket-based locks (with/without broadcast protocol)
- Pure NIO locking
- Simple lock file (no heartbeat or polling)
- JNI and native configuration alternatives
As a final note and sign post for developers starting to work with Java NIO:
A separate NIOLockFile descendent exists specifically because it was determined though experimenatation 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 abscence 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.
- Since:
- 1.7.2
- A file with a commonly agreed-upon path is used to implement
cooperative locking semantics regarding another set of files with
commonly agreed-upon paths.
-
-
Nested Class Summary
Nested Classes Modifier and Type Class and Description static classLockFile.BaseExceptionBase exception class for lock condition specific exceptions.static classLockFile.FileCanonicalizationExceptionThrown when canonicalization of a LockFile object's target file path fails.static classLockFile.FileSecurityExceptionThrown when access to a LockFile object's target file raises a Java SecurityEception.static classLockFile.LockHeldExternallyExceptionThrown when an externally held lock condition prevents lock aquisition.static classLockFile.UnexpectedEndOfFileExceptionThrown when access to a LockFile object's target file raises an unexpected EOFException.static classLockFile.UnexpectedFileIOExceptionThrown when access to a LockFile object's target file raises an unexpected IOException other than EOFException.static classLockFile.UnexpectedFileNotFoundExceptionThrown when access to a LockFile object's target file raises an unexpected FileNotFoundException.static classLockFile.WrongLengthExceptionThrown when it is detected that a LockFile object's target file does not have the expected length.static classLockFile.WrongMagicExceptionThrown when it is detected that a LockFile object's target file does not start with the expected MAGIC value.
-
Field Summary
Fields Modifier and Type Field and Description static longHEARTBEAT_INTERVALArbitary period, in milliseconds, at which heartbeat timestamps are written to this object's lock file.static longHEARTBEAT_INTERVAL_PADDEDHEARTBEAT_INTERVAL+ 100.static booleanNIO_FILELOCK_AVAILABLEStatically computed indication of java.nio.channels.FileLock runtime availability.static java.lang.ClassNIO_LOCKFILE_CLASSStatically computed reference to the NIOLockFile class.static java.lang.StringPOLL_INTERVAL_PROPERTYSystem property that can be used to override the default number of milliseconds between each heartbeat poll retry.static intPOLL_RETRIES_DEFAULTNumber of retries used by default inpollHeartbeat.static java.lang.StringPOLL_RETRIES_PROPERTYSystem property that can be used to override the default number of heartbeat poll retries.static booleanUSE_NIO_FILELOCK_DEFAULTWhether java.nio file locking is attempted by default.static java.lang.StringUSE_NIO_FILELOCK_PROPERTYSystem property that can be used to control whether nio file locking is attempted.static intUSED_REGIONSize, in bytes, of the region at the beginning of a lock file that is actually used to record lock information.
-
Method Summary
All Methods Static Methods Instance Methods Concrete Methods Modifier and Type Method and Description booleanequals(java.lang.Object obj)Tests whether some other object is "equal to" this one.java.lang.StringgetCanonicalPath()Retrieves the canonical path of this object's lock file, as a String object.longgetPollHeartbeatInterval()Retrieves the interval, in milliseconds, that pollHeartbeat waits between failed invocations of checkHeartbeat.intgetPollHeartbeatRetries()Retrieves the number of times checkHeartbeat may fail before pollHeartbeat fails as a consequence.inthashCode()Retrieves the hash code value for this object.booleanisLocked()Retrieves whether this object has successfully obtained and is still holding (has not yet released) a cooperative lock condition on its lock file.static booleanisLocked(java.lang.String path)Retrieves whether there is potentially already a cooperative lock, operating system lock or some other situation preventing a cooperative lock condition from being aquired using the specified path.booleanisValid()Retrieves whether this object holds a valid lock condition on its lock file.static LockFilenewLockFile(java.lang.String path)Retrieves a LockFile instance, initialized with a File object whose path is the canonical form of the one specified by the given path argument.static LockFilenewLockFileLock(java.lang.String path)Logger.acquireLock(java.lang.String)delegate.java.lang.StringtoString()Retrieves a String representation of this object.booleantryLock()Attempts to obtain a cooperative lock condition upon this object's lock file.booleantryRelease()Attempts to release any cooperative lock condition this object may hold upon its lock file.
-
-
-
Field Detail
-
HEARTBEAT_INTERVAL
public static final long HEARTBEAT_INTERVAL
Arbitary period, in milliseconds, at which heartbeat timestamps are written to this object's lock file.This value was selected to be very conservative, just in case timing jitters are experienced on the order introduced by brief network partitions, accidentally removed media and transient high load CPU bursts.
- See Also:
- Constant Field Values
-
HEARTBEAT_INTERVAL_PADDED
public static final long HEARTBEAT_INTERVAL_PADDED
HEARTBEAT_INTERVAL+ 100.Interval used by
checkHeartbeatto test whether the timestamp in the underlying lock file is live or stale. Padding added in the hope of reducing potential timing jitter issues under the polling scheme introduced in 1.8.0.3- See Also:
- Constant Field Values
-
USED_REGION
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.Value is currently MAGIC.length + sizeof(long) = (8 + 8) = 16
- See Also:
- Constant Field Values
-
POLL_RETRIES_DEFAULT
public static final int POLL_RETRIES_DEFAULT
Number of retries used by default inpollHeartbeat.- See Also:
- Constant Field Values
-
POLL_RETRIES_PROPERTY
public static final java.lang.String POLL_RETRIES_PROPERTY
System property that can be used to override the default number of heartbeat poll retries.- See Also:
- Constant Field Values
-
POLL_INTERVAL_PROPERTY
public static final java.lang.String POLL_INTERVAL_PROPERTY
System property that can be used to override the default number of milliseconds between each heartbeat poll retry.- See Also:
- Constant Field Values
-
USE_NIO_FILELOCK_DEFAULT
public static final boolean USE_NIO_FILELOCK_DEFAULT
Whether java.nio file locking is attempted by default.- See Also:
- Constant Field Values
-
USE_NIO_FILELOCK_PROPERTY
public static final java.lang.String USE_NIO_FILELOCK_PROPERTY
System property that can be used to control whether nio file locking is attempted.- See Also:
- Constant Field Values
-
NIO_FILELOCK_AVAILABLE
public static final boolean NIO_FILELOCK_AVAILABLE
Statically computed indication of java.nio.channels.FileLock runtime availability.Design Note:
Computed in a static initializer block. Will be false if USE_NIO_FILELOCK_PROPERTY is false at static initialization, regardless of actual availability.
-
NIO_LOCKFILE_CLASS
public static final java.lang.Class NIO_LOCKFILE_CLASS
Statically computed reference to the NIOLockFile class.Design Note:
Computed in a static initializer block. Will be null if USE_NIO_FILELOCK_PROPERTY is false at static initialization, regardless of actual availability.
-
-
Method Detail
-
newLockFile
public static final LockFile newLockFile(java.lang.String path) throws LockFile.FileCanonicalizationException, LockFile.FileSecurityException
Retrieves a LockFile instance, initialized with a File object whose path is the canonical form of the one specified by the given path argument.The resulting LockFile instance does not yet hold a lock condition on the file with the given path, nor does it guarantee that the file pre-exists or is created. However, upon successful execution, it is guaranteed that all required parent directories have been created and that the underlying platform has verified the specified path is legal on the file system of the underlying storage partition.
- Parameters:
path- the path of the File object with which the retrieved LockFile object is to be initialized- Returns:
- a LockFile instance initialized with a File object whose path is the one specified by the given path argument.
- Throws:
LockFile.FileCanonicalizationException- if an I/O error occurs upon canonicalization of the given path, which is possible because it may be illegal on the runtime file system or because construction of the canonical path name may require native file system queriesLockFile.FileSecurityException- if a required system property value cannot be accessed, or if a security manager exists and itsSecurityManager.checkRead(java.io.FileDescriptor)method denies read access to the file; or if itsSecurityManager.checkRead(java.lang.String)method does not permit verification of the existence of all necessary parent directories; or if theSecurityManager.checkWrite(java.lang.String)method does not permit all necessary parent directories to be created
-
newLockFileLock
public static final LockFile newLockFileLock(java.lang.String path) throws HsqlException
Logger.acquireLock(java.lang.String)delegate.Retrieves a new LockFile object holding a cooperative lock condition upon the file with the given path, appended with the extension '.lck'.
- Parameters:
path- of the lock file, to which will be appended '.lck'- Returns:
- a new LockFile object holding a cooperative lock condition upon the file with the given path, appended with the extension '.lck'
- Throws:
HsqlException- if the lock condition cannot be obtained for any reason.
-
equals
public final boolean equals(java.lang.Object obj)
Tests whether some other object is "equal to" this one.An object is considered equal to a LockFile object if and only if it is not null, it is an instance of LockFile and either it is the identical instance or it has the same lock file. More formally, is is considered equal if and only if it is not null, it is an instance of LockFile, and the expression:
this == other || this.file == null ? other.file == null : this.file.equals(other.file);
yeilds true.Note that file must be a canonical reference to correctly satisfy this contract.
- Overrides:
equalsin classjava.lang.Object- Parameters:
obj- the reference object with which to compare.- Returns:
- true if this object is equal to the obj argument; false otherwise.
- See Also:
hashCode()
-
getCanonicalPath
public final java.lang.String getCanonicalPath()
Retrieves the canonical path of this object's lock file, as a String object.- Returns:
- the canonical path of this object's lock file.
-
hashCode
public final int hashCode()
Retrieves the hash code value for this object.The value is zero if file is null, else the hashCode of file. That is, two LockFile objects have the same hashCode value if they refer to the same lock file.
Note that file must be a canonical reference to correctly satisfy this contract.
- Overrides:
hashCodein classjava.lang.Object- Returns:
- a hash code value for this object.
- See Also:
equals(java.lang.Object)
-
isLocked
public final boolean isLocked()
Retrieves whether this object has successfully obtained and is still holding (has not yet released) a cooperative lock condition on its lock file.Note:
Due to platform-independence retrictions placed on a JVM, it is quite possible to successfully acquire a lock condition and yet for the condition to become invalid while still held.
For instance, under JVMs with no java.nio package or under operating systems that do not apply mandatory file locking (espcially mandatory locking that precludes deletion), it is quite possible for another process or even an uncooperative bit of code running in the same JVM to overwrite or delete the target lock file while this object holds a lock condition.
Because of this, the isValid() method is provided in the public interface in order to allow clients to detect at least a subset of such situations.
- Returns:
- true if this object has successfully obtained and is still holding (has not yet released) a lock condition, else false
- See Also:
isValid()
-
isLocked
public static final boolean isLocked(java.lang.String path)
Retrieves whether there is potentially already a cooperative lock, operating system lock or some other situation preventing a cooperative lock condition from being aquired using the specified path.- Parameters:
path- the path to test- Returns:
- true if there is currently something preventing the acquisition of a cooperative lock condition using the specified path, else false
-
isValid
public boolean isValid()
Retrieves whether this object holds a valid lock condition on its lock file.More formally, this method retrieves true if and only if:
isLocked() && file != null && file.exists() && raf != null
- Returns:
- true if this object holds a valid lock condition on its lock file; else false
- Throws:
java.lang.SecurityException- if a required system property value cannot be accessed, or if a Java security manager exists and its checkRead method denies read access to the lock file;
-
toString
public java.lang.String toString()
Retrieves a String representation of this object.The String is of the form:
super.toString() + "[file=" + getCanonicalPath() + ", exists=" + file.exists() + ", locked=" + isLocked() + ", valid=" + isValid() + ", " + toStringImpl() + "]";
- Overrides:
toStringin classjava.lang.Object- Returns:
- a String representation of this object.
- Throws:
java.lang.SecurityException- if a required system property value cannot be accessed, or if a security manager exists and itsSecurityManager.checkRead(java.io.FileDescriptor)method denies read access to the lock file;- See Also:
toStringImpl()
-
getPollHeartbeatRetries
public int getPollHeartbeatRetries()
Retrieves the number of times checkHeartbeat may fail before pollHeartbeat fails as a consequence.The value is obtained in the following manner:
- retries is assigned POLL_RETRIES_DEFAULT.
- retries is assigned Integer.getInteger(POLL_RETRIES_PROPERTY, retries) inside a try-catch block to silently ignore any security exception.
- If retries is less than one (1), retries is assigned one (1).
- Returns:
- the number of times checkHeartbeat may fail before pollHeartbeat fails as a consequence.
-
getPollHeartbeatInterval
public long getPollHeartbeatInterval()
Retrieves the interval, in milliseconds, that pollHeartbeat waits between failed invocations of checkHeartbeat. The value is obtained in the following manner:- interval is assigned 10 + (HEARTBEAT_INTERVAL_PADDED getPollHeartbeatRetries())
- interval is assigned Long.getLong(POLL_INTERVAL_PROPERTY, interval), inside a try-catch block, to silently ignore any security exception.
- If interval is less than or equal to zero (0), interval is reassigned 10 + (HEARTBEAT_INTERVAL_PADDED / getPollHeartbeatRetries())
- Returns:
- the interval, in milliseconds, that pollHeartbeat waits between failed invocations of checkHeartbeat
-
tryLock
public final boolean tryLock() throws LockFile.FileSecurityException, LockFile.LockHeldExternallyException, LockFile.UnexpectedFileNotFoundException, LockFile.UnexpectedEndOfFileException, LockFile.UnexpectedFileIOException, LockFile.WrongLengthException, LockFile.WrongMagicExceptionAttempts to obtain a cooperative lock condition upon this object's lock file.- Returns:
- true if this object already holds a lock or the lock was obtained successfully, else false
- Throws:
LockFile.FileSecurityException- if the lock condition could not be obtained due to a Java security permission violationLockFile.LockHeldExternallyException- if the lock condition could not be obtained because the target file's heartbeat timestamp indicated that a lock condition was held by another LockFile.LockFile.UnexpectedFileNotFoundException- if the lock condition could not be obtained because the target file became unavailable between a successful test for existence and an attempt to read its MAGIC or heartbeat timestamp value.LockFile.UnexpectedEndOfFileException- if the lock condition could not be obtained because EOFException was raised while trying to read the MAGIC or heartbeat timestamp value of the target fileLockFile.UnexpectedFileIOException- if the lock condition could not be obtained due to an IOException other than EOFExceptionLockFile.WrongLengthException- if the lock condition could not be obtained because the target file was the wrong lengthLockFile.WrongMagicException- if the lock condition could not be obtained because the target file had the wrong MAGIC value
-
tryRelease
public final boolean tryRelease() throws LockFile.FileSecurityException, LockFile.UnexpectedFileIOExceptionAttempts to release any cooperative lock condition this object may hold upon its lock file.- Returns:
- true if this object does not currently hold a lock condition or the lock is released completely (including successful file deletion), else false.
- Throws:
LockFile.FileSecurityException- if a SecurityException is raised in the process of releasing the lock conditionLockFile.UnexpectedFileIOException- if an IoException is raised in the process of releasing the lock condition
-
-
DataMelt 3.0 © DataMelt by jWork.ORG