Documentation of 'org.hsqldb.persist.LockFile' Java class
LockFile
org.hsqldb.persist

Class LockFile



  • public class LockFile
    extends java.lang.Object
    Base 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:

    1. 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.

    2. 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.

    3. 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.

    4. The generic rules used to aquire a cooperative lock condition are as follows:

      1. If a lock condition is already held by this object, do nothing and signify that the lock attempt was successful, else...

      2. 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.

      3. For each poll:

        1. 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...

        2. 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).

        3. 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.

        4. Test the file's length, fast failing the current poll if the length cannot be determined or it is not the expected value.

        5. Open a stream to read the file's MAGIC and heartbeat timestamp values, fast failing the current poll if the stream cannot be opened.

        6. Test the file's MAGIC value, failing the current poll if the value cannot be read or it is not the expected value.

        7. 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).

      4. 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...

      5. 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.

    5. The generic rules used to release a cooperative lock condition are:

      1. If a lock condition is not currently held, do nothing and signify success, else...

      2. 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.


    Additionally, 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 NIOLockFile instances.

    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_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 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
    Of course, discussions involving and patches implementing improvements or better alternatives are always welcome.

    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
    • Field Summary

      Fields 
      Modifier and Type Field and Description
      static long HEARTBEAT_INTERVAL
      Arbitary period, in milliseconds, at which heartbeat timestamps are written to this object's lock file.
      static long HEARTBEAT_INTERVAL_PADDED
      static boolean NIO_FILELOCK_AVAILABLE
      Statically computed indication of java.nio.channels.FileLock runtime availability.
      static java.lang.Class NIO_LOCKFILE_CLASS
      Statically computed reference to the NIOLockFile class.
      static java.lang.String POLL_INTERVAL_PROPERTY
      System property that can be used to override the default number of milliseconds between each heartbeat poll retry.
      static int POLL_RETRIES_DEFAULT
      Number of retries used by default in pollHeartbeat.
      static java.lang.String POLL_RETRIES_PROPERTY
      System property that can be used to override the default number of heartbeat poll retries.
      static boolean USE_NIO_FILELOCK_DEFAULT
      Whether java.nio file locking is attempted by default.
      static java.lang.String USE_NIO_FILELOCK_PROPERTY
      System property that can be used to control whether nio file locking is attempted.
      static int USED_REGION
      Size, 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
      boolean equals(java.lang.Object obj)
      Tests whether some other object is "equal to" this one.
      java.lang.String getCanonicalPath()
      Retrieves the canonical path of this object's lock file, as a String object.
      long getPollHeartbeatInterval()
      Retrieves the interval, in milliseconds, that pollHeartbeat waits between failed invocations of checkHeartbeat.
      int getPollHeartbeatRetries()
      Retrieves the number of times checkHeartbeat may fail before pollHeartbeat fails as a consequence.
      int hashCode()
      Retrieves the hash code value for this object.
      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.
      static 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.
      boolean isValid()
      Retrieves whether this object holds a valid lock condition on its lock file.
      static LockFile newLockFile(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 LockFile newLockFileLock(java.lang.String path)
      java.lang.String toString()
      Retrieves a String representation of this object.
      boolean tryLock()
      Attempts to obtain a cooperative lock condition upon this object's lock file.
      boolean tryRelease()
      Attempts to release any cooperative lock condition this object may hold upon its lock file.
      • Methods inherited from class java.lang.Object

        getClass, notify, notifyAll, wait, wait, wait
    • 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 checkHeartbeat to 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_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 queries
        LockFile.FileSecurityException - if a required system property value cannot be accessed, or if a security manager exists and its SecurityManager.checkRead(java.io.FileDescriptor) method denies read access to the file; or if its SecurityManager.checkRead(java.lang.String) method does not permit verification of the existence of all necessary parent directories; or if the SecurityManager.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:
        equals in class java.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:
        hashCode in class java.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:
        toString in class java.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 its SecurityManager.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:

        1. retries is assigned POLL_RETRIES_DEFAULT.
        2. retries is assigned Integer.getInteger(POLL_RETRIES_PROPERTY, retries) inside a try-catch block to silently ignore any security exception.
        3. 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:

        1. interval is assigned 10 + (HEARTBEAT_INTERVAL_PADDED getPollHeartbeatRetries())
        2. interval is assigned Long.getLong(POLL_INTERVAL_PROPERTY, interval), inside a try-catch block, to silently ignore any security exception.
        3. 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

DataMelt 3.0 © DataMelt by jWork.ORG

Ads help maintain this website.