/* Copyright (c) 2001-2019, 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.EOFException;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.RandomAccessFile;

import org.hsqldb.DatabaseManager;
import org.hsqldb.HsqlDateTime;
import org.hsqldb.HsqlException;
import org.hsqldb.error.Error;
import org.hsqldb.error.ErrorCode;
import org.hsqldb.lib.FileUtil;
import org.hsqldb.lib.HsqlTimer;
import org.hsqldb.lib.StringConverter;

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 acquire 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 exception 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 exception 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 mandatory 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 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:

  • 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 descendant exists specifically because it was determined though 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.

Author:Campbell Burnet (campbell-burnet@users dot sourceforge.net)
Version:2.5.0
Since:1.7.2
/** * Base cooperative file locking implementation and <tt>LockFile</tt> * factory. <p> * * <hr> * * Provides a facility for cooperative file locking across process boundaries * and isolated in-process class loader contexts. <p> * * 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<a href="#note1"> * <sup>*</sup></a> 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. <p> * * <a name="note1"><sup>*</sup></a> * 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. <p> * * <hr> * * Here is the way this class presently operates: <p> * * <ol style="list-style-type: upper-latin"> * <li>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.<p> * * <li>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.<p> * * <li>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.<p> * * <li>The generic rules used to acquire a cooperative lock condition are * as follows:<p> * * <ol> * <li>If a lock condition is already held by this object, do nothing and * signify that the lock attempt was successful, else...<p> * * <li>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.<p> * * <li>For each poll:<p> * * <ol style="list-style-type: lower-roman"> * * <li>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 exception is thrown in response * to the attempt, else...<p> * * <li>Test if the underlying file exists, fast failing the current * poll if it is impossible to determine (i.e. if a security * exception is thrown).<p> * * <li>If the file does not exist, exit the polling loop immediately * indicating success.<p> * * This can occur only under pre-JDK 1.2 runtimes; or when the * underlying platform does not correctly support {@link * java.io.File#createNewFile()}; or when the underlying file is * deleted within a very short time after i.), above (typically * on the order of microseconds). <p> * * If the underlying platform employs a kernel-enforced mandatory * file locking blanket policy for open files (e.g. <em>Windows * </em><sup>tm</sup>), then this is likely a non-issue. And if * this case makes possible a race condition with another * <tt>LockFile</tt> object (because the test for existence and * subsequent file creation is not atomic relative to all other * file system actions), it is still <em>very</em> unlikely that * so unfortunate a timing will occur as to allow simultaneous * lock conditions to be established. Finally, if some * non-<tt>LockFile</tt> 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.<p> * * <li>Test the file's length, fast failing the current poll if the * length cannot be determined or it is not the expected * value.<p> * * <li>Open a stream to read the file's <tt>MAGIC</tt> and heartbeat * timestamp values, fast failing the current poll if the stream * cannot be opened.<p> * * <li>Test the file's <tt>MAGIC</tt> value, failing the current poll * if the value cannot be read or it is not the expected * value.<p> * * <li>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).<p> * </ol> * <li>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: <p> * * <ul> * <li>The file had the wrong length or <tt>MAGIC</tt> value (was * not an HSQLDB lock file). * * <li>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. * * <li>An incompatible OS-enforced security restriction was in * effect. * * <li>An incompatible Java-enforced security restriction was * in effect. * * <li>The target file system media was effectively inaccessible. * <li>A cooperative lock condition was held by some other * <tt>LockFile</tt>. * * <li>A kernel-enforced mandatory or advisory file lock was held. * </ul> <p> * * In this case, signify failure indicating the last encountered * reason, else...<p> * * <li>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.<p> * </ol> * <li>The generic rules used to release a cooperative lock condition are:<p> * <ol> * <li>If a lock condition is not currently held, do nothing and signify * success, else...<p> * * <li>A lock condition is currently held by this object, so try to * release it. <p> * * 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 <em>might</em> have failed. <p> * </ol> * </ol> <p> * * <hr> * * Additionally, {@link #doOptionalLockActions() doOptionalLockActions()} and * {@link #doOptionalReleaseActions() 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 * {@link #newLockFile newLockFile()}.<p> * * In particular, if {@link #USE_NIO_FILELOCK_PROPERTY} is true and the required * classes are available at static initialization, then <tt>newLockFile()</tt> * produces org.hsqldb.persist.NIOLockFile instances.<p> * * When <tt>NIOLockFile</tt> 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, <tt>newLockFile()</tt> produces vanilla <tt>LockFile</tt> * instances, which exhibit just the elementary cooperative locking behavior on * platforms that do not, by default, implement kernel-enforced mandatory * locking for open files. <p> * * 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 <em>does</em> * 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. <p> * * 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).<p> * * It is for the preceding reasons that, as of HSQLDB 1.8.0.3, * <tt>FileLock</tt>'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 * <tt>FileLock</tt> and failed, then the enclosing cooperative lock attempt * failed also, despite the fact that a vanilla locking approach could * succeed. <p> * * <b>Polling Configuration</b>:<p> * * Although the {@link #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. <p> * * 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. <p> * * At present, this can be done at any time by setting the system properties * whose names are {@link #POLL_RETRIES_PROPERTY} and {@link * #POLL_INTERVAL_PROPERTY}. <p> * * 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. <p> * * <b>NIO Configuration</b>:<p> * * 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 {@link * #USE_NIO_FILELOCK_PROPERTY} true at JVM startup (for example, by using a * command-line <tt>-D&lt;property-name&gt;=true</tt> directive). Be aware that * the system property value is read only once, in the static initializer block * for this class. <p> * * <b>Design Notes</b>:<p> * * First, it should be noted that no thread synchronization occurs in * this class. Primarily, this is because the standard entry point, * {@link #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. <p> * * 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.<p> * * Alternate approaches that have been considered and rejected for now * include: <p> * * <ul> * <li>Socket-based locks (with/without broadcast protocol) * <li>Pure NIO locking * <li>Simple lock file (no heartbeat or polling) * <li>JNI and native configuration alternatives * </ul> * * Of course, discussions involving and patches implementing improvements * or better alternatives are always welcome. <p> * * As a final note and sign post for developers starting to work with * Java NIO: <p> * * A separate <tt>NIOLockFile</tt> descendant exists specifically * because it was determined though experimentation that * <tt>java.nio.channels.FileLock</tt> 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 * <tt>FileLock.release()</tt> 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 <tt>LockFile</tt> * instances, other in-process objects or other processes to successfully obtain * a lock condition on the lock file, despite the fact that the * <tt>FileLock</tt> 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 <tt>FileLock.tryLock()</tt> * method was reflectively invoked. <p> * * To solve this, the original <tt>LockFile</tt> class was split in two and * instead of reflective method invocation, subclass instantiation is now * performed at the level of the <tt>newLockFile()</tt> 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 * <tt>NIOLockFile</tt> to the hsqldb.jar if such features are reported * present. <p> * * @author Campbell Burnet (campbell-burnet@users dot sourceforge.net) * @version 2.5.0 * @since 1.7.2 */
public class LockFile {
Arbitrary 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.

/** * Arbitrary period, in milliseconds, at which heartbeat timestamps are * written to this object's lock file. <p> * * 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. */
public static final long HEARTBEAT_INTERVAL = 10000;
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

/** * {@link #HEARTBEAT_INTERVAL} + 100. <p> * * Interval used by {@link #checkHeartbeat(boolean) 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 */
public static final long HEARTBEAT_INTERVAL_PADDED = 10100;
Value written at the beginning of an HSQLDB lock file to distinguish it from other file types.

The value is the octet sequence: {0x48, 0x53, 0x51, 0x4c, 0x4c, 0x4f, 0x43, 0x4b}, which is the ASCII sequence {'H', 'S', 'Q', 'L', 'L', 'O', 'C', 'K'}.

Design Note:

"HSQLLOCK".getBytes() is no longer used because it is dependent on the underlying platform's default character set.

/** * Value written at the beginning of an HSQLDB lock file to distinguish it * from other file types. <p> * * The value is the octet sequence: {0x48, 0x53, 0x51, 0x4c, 0x4c, 0x4f, * 0x43, 0x4b}, which is the ASCII sequence {'H', 'S', 'Q', 'L', 'L', 'O', * 'C', 'K'}. <p> * * <b>Design Note</b>: <p> * * "HSQLLOCK".getBytes() is no longer used because it is dependent on the * underlying platform's default character set. */
protected static final byte[] MAGIC = { 0x48, 0x53, 0x51, 0x4c, 0x4c, 0x4f, 0x43, 0x4b };
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

/** * Size, in bytes, of the region at the beginning of a lock file that is * actually used to record lock information. <p> * * Value is currently MAGIC.length + sizeof(long) = (8 + 8) = 16 */
public static final int USED_REGION = 16;
Number of retries used by default in pollHeartbeat.
/** * Number of retries used by default in {@link #pollHeartbeat() * pollHeartbeat}. */
public static final int POLL_RETRIES_DEFAULT = 10;
System property that can be used to override the default number of heartbeat poll retries.
/** * System property that can be used to override the default number of * heartbeat poll retries. */
public static final String POLL_RETRIES_PROPERTY = "hsqldb.lockfile.poll.retries";
System property that can be used to override the default number of milliseconds between each heartbeat poll retry.
/** * System property that can be used to override the default number of * milliseconds between each heartbeat poll retry. */
public static final String POLL_INTERVAL_PROPERTY = "hsqldb.lockfile.poll.interval";
Whether java.nio file locking is attempted by default.
/** Whether <tt>java.nio</tt> file locking is attempted by default. */
public static final boolean USE_NIO_FILELOCK_DEFAULT = false;
System property that can be used to control whether nio file locking is attempted.
/** * System property that can be used to control whether nio file locking is * attempted. */
public static final String USE_NIO_FILELOCK_PROPERTY = "hsqldb.lockfile.nio.filelock";
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.

/** * Statically computed indication of <tt>java.nio.channels.FileLock</tt> * runtime availability. <p> * * <b>Design Note</b>:<p> * * Computed in a static initializer block. Will be <tt>false</tt> if * <tt>USE_NIO_FILELOCK_PROPERTY</tt> is <tt>false</tt> at static * initialization, regardless of actual availability. */
public static final boolean NIO_FILELOCK_AVAILABLE;
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.

/** * Statically computed reference to the <tt>NIOLockFile</tt> class. <p> * * <b>Design Note</b>:<p> * * Computed in a static initializer block. Will be <tt>null</tt> if * <tt>USE_NIO_FILELOCK_PROPERTY</tt> is <tt>false</tt> at static * initialization, regardless of actual availability. */
public static final Class<?> NIO_LOCKFILE_CLASS;
The timed scheduler with which to register this object's heartbeat task.
/** * The timed scheduler with which to register this object's * heartbeat task. */
protected static final HsqlTimer timer = DatabaseManager.getTimer(); // This static initializer comes last, since it references a subclass // // That is, it is best practice to ensure the static fields of this class // are all initialized before referencing a subclass whose static // field initialization may in turn reference static fields in this class. static { synchronized (LockFile.class) { boolean use = USE_NIO_FILELOCK_DEFAULT; try { use = "true".equalsIgnoreCase( System.getProperty(USE_NIO_FILELOCK_PROPERTY, use ? "true" : "false")); } catch (Exception e) {} boolean avail = false; Class clazz = null; if (use) { try { Class.forName("java.nio.channels.FileLock"); clazz = Class.forName("org.hsqldb.persist.NIOLockFile"); avail = true; } catch (Exception e) {} } NIO_FILELOCK_AVAILABLE = avail; NIO_LOCKFILE_CLASS = clazz; } }
Canonical reference to this object's lock file.

Design Note:

Should really be final, but finality makes reflective construction and adherence to desirable LockFile factory method event sequence more complicated.

/** * Canonical reference to this object's lock file. <p> * * <b>Design Note</b>:<p> * * Should really be final, but finality makes reflective construction * and adherence to desirable <tt>LockFile</tt> factory method event * sequence more complicated. */
protected File file;
Cached value of the lock file's canonical path Design Note:

Should really be final, but finality makes reflective construction and adherence to desirable LockFile factory method event sequence much more complicated.

/** * Cached value of the lock file's canonical path * * <b>Design Note</b>:<p> * * Should really be final, but finality makes reflective construction * and adherence to desirable <tt>LockFile</tt> factory method event * sequence much more complicated. */
private String cpath;
A RandomAccessFile constructed from this object's canonical file reference.

This RandomAccessFile is used to periodically write out the heartbeat timestamp to this object's lock file.

/** * A <tt>RandomAccessFile</tt> constructed from this object's canonical file * reference. <p> * * This <tt>RandomAccessFile</tt> is used to periodically write out the * heartbeat timestamp to this object's lock file. */
protected volatile RandomAccessFile raf;
Indicates presence or absence of the cooperative lock condition.
/** Indicates presence or absence of the cooperative lock condition. */
protected volatile boolean locked;
Opaque reference to this object's heartbeat task.
/** Opaque reference to this object's heartbeat task. */
private volatile Object timerTask;
Retrieves a new NIOLockFile, or null if not available under the current runtime environment.
Returns:a new NIOLockFile, or null if not available under the current runtime environment
/** * Retrieves a new <tt>NIOLockFile</tt>, or <tt>null</tt> if not available * under the current runtime environment. * * @return a new <tt>NIOLockFile</tt>, or <tt>null</tt> if not available * under the current runtime environment */
private static LockFile newNIOLockFile() { if (NIO_FILELOCK_AVAILABLE && NIO_LOCKFILE_CLASS != null) { try { return (LockFile) NIO_LOCKFILE_CLASS.getDeclaredConstructor().newInstance(); } catch (Exception e) { // e.printStackTrace() } } return null; }
To allow subclassing without exposing a public constructor.
/** * To allow subclassing without exposing a public constructor. */
protected LockFile() {}
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.

Params:
  • path – the path of the File object with which the retrieved LockFile object is to be initialized
Throws:
Returns:a LockFile instance initialized with a File object whose path is the one specified by the given path argument.
/** * Retrieves a <tt>LockFile</tt> instance, initialized with a <tt>File</tt> * object whose path is the canonical form of the one specified by the * given <tt>path</tt> argument. <p> * * The resulting <tt>LockFile</tt> 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. * * @return a <tt>LockFile</tt> instance initialized with a <tt>File</tt> * object whose path is the one specified by the given <tt>path</tt> * argument. * @param path the path of the <tt>File</tt> object with which the retrieved * <tt>LockFile</tt> object is to be initialized * @throws 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 * @throws FileSecurityException if a required system property value cannot * be accessed, or if a security manager exists and its <tt>{@link * java.lang.SecurityManager#checkRead}</tt> method denies read * access to the file; or if its <tt>{@link * java.lang.SecurityManager#checkRead(java.lang.String)}</tt> * method does not permit verification of the existence of all * necessary parent directories; or if the <tt>{@link * java.lang.SecurityManager#checkWrite(java.lang.String)}</tt> * method does not permit all necessary parent directories to be * created */
public static LockFile newLockFile(final String path) throws FileCanonicalizationException, FileSecurityException { LockFile lockFile = newNIOLockFile(); if (lockFile == null) { lockFile = new LockFile(); } lockFile.setPath(path); return lockFile; }
Logger.acquireLock(String) delegate.

Retrieves a new LockFile object holding a cooperative lock condition upon the file with the given path, appended with the extension '.lck'.

Params:
  • path – of the lock file, to which will be appended '.lck'
Throws:
  • HsqlException – if the lock condition cannot be obtained for any reason.
Returns:a new LockFile object holding a cooperative lock condition upon the file with the given path, appended with the extension '.lck'
/** * {@link org.hsqldb.persist.Logger#acquireLock(java.lang.String)} * delegate.<p> * * Retrieves a new <tt>LockFile</tt> object holding a cooperative lock * condition upon the file with the given path, appended with the * extension '.lck'. <p> * * @param path of the lock file, to which will be appended '.lck' * @throws org.hsqldb.HsqlException if the lock condition cannot * be obtained for any reason. * @return a new <tt>LockFile</tt> object holding a cooperative lock * condition upon the file with the given path, appended with the * extension '.lck' */
public static LockFile newLockFileLock(final String path) throws HsqlException { LockFile lockFile = null; try { lockFile = LockFile.newLockFile(path + ".lck"); } catch (LockFile.BaseException e) { throw Error.error(ErrorCode.LOCK_FILE_ACQUISITION_FAILURE, e.getMessage()); } boolean locked = false; try { locked = lockFile.tryLock(); } catch (LockFile.BaseException e) { throw Error.error(ErrorCode.LOCK_FILE_ACQUISITION_FAILURE, e.getMessage()); } // Paranoia mode: In theory, this case can't happen, given the way // tryLock now works; by all current understanding of the involved API // contracts, an exception will always be thrown instead by the code // above. if (!locked) { throw Error.error(ErrorCode.LOCK_FILE_ACQUISITION_FAILURE, lockFile.toString()); } return lockFile; }
Checks 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 LockFile instance) or stale.

The check conforms to the following rules:

  1. If the parameter withCreateNewFile is true, File.createNewFile() is available and its invocation upon this object's file object indicates the underlying file was atomically created if and only if it did not yet exist, then return immediately (we have won the race to establish a lock file).

  2. Test again if the file exists, returning immediately if it does not (there's no file and hence no heartbeat to check).

    An immediate return can occur here 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. Windowstm ), 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 yields false 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 by some uncooperative process.

  3. If a Java security exception is thrown while testing for existence, it is rethrown as a FileSecurityException.
  4. Read the file's length.
  5. If a Java security exception is thrown reading length, it is rethrown as a FileSecurityException (it is possible somebody concurrently refreshed the system Policy in the interim).
  6. If the file does not have the expected length, a WrongLengthException is thrown (we're trying to check something that is not an HSQLDB lock file).
  7. Open an input steam to read the file's MAGIC and heartbeat timestamp values.
  8. If a file not found exception is thrown above, it is rethrown as an UnexpectedFileNotFoundException (we've already tested for existence).
  9. If a Java security exception is thrown above, it is rethrown as a FileSecurityException (it is possible somebody concurrently refreshed the system Policy in the interim).
  10. Read the MAGIC value.
  11. If an end of file exception is thrown above, it is rethrown as an UnexpectedEndOfFileException (we've already tested the length... did someone truncate the file in the interim?).
  12. If an I/O exception is thrown, it is rethrown as an UnexpectedFileIOException (we've already tested for existence, length and successfully opened a stream...did someone, for example, force unmount or physically remove the underlying device in the interim?)
  13. If the value read in does not match the expected MAGIC value, a WrongMagicException is thrown (we're trying to check something that is not an HSQLDB lock file).
  14. Read the heartbeat timestamp.
  15. If a Java security exception is thrown above, it is rethrown as a FileSecurityException (it is possible somebody concurrently refreshed the system Policy in the interim).
  16. If an end of file execution is thrown above, it is rethrown as an UnexpectedEndOfFileException (we've already tested the length... did someone truncate the file in the interim?).
  17. If an I/O exception is thrown, it is rethrown as an UnexpectedFileIOException (we've already tested for existence, length and successfully opened a stream...did someone, for example, force unmount or physically remove the underlying device in the interim?)
  18. If the timestamp read in is less than or equal to HEARTBEAT_INTERVAL_PADDED milliseconds into the past or future, then a LockHeldExternallyException is thrown.
  19. Otherwise, this method simply returns.
Params:
  • withCreateNewFile – if true, attempt to employ File.createNewFile() as part of the check so as to eliminate potential race conditions when establishing a new lock file
Throws:
/** * Checks 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 <tt>LockFile</tt> instance) or stale. <p> * * The check conforms to the following rules: <p> * * <ol> * <li>If the parameter <tt>withCreateNewFile</tt> is true, {@link * java.io.File#createNewFile()} is available and its invocation * upon this object's <tt>file</tt> object indicates the underlying * file was atomically created if and only if it did not yet exist, * then return immediately (we have won the <em>race</em> to establish * a lock file). <p> * * <li>Test again if the file exists, returning immediately if it does not * (there's no file and hence no heartbeat to check). <p> * * An immediate return can occur here only under pre-JDK 1.2 runtimes; * or when the underlying platform does not correctly support * <tt>File.createNewFile()</tt>; or when the underlying file is deleted * within a very short time after i.), above (typically on the order of * microseconds). <p> * * If the underlying platform employs a kernel-enforced mandatory file * locking blanket policy for open files (e.g. <em>Windows</em><sup>tm * </sup>), then this is likely a non-issue. And if this case makes * possible a race condition with another <tt>LockFile</tt> object * (because the test for existence yields false and subsequent file * creation is not atomic relative to all other file system actions), it * is still <em>very</em> unlikely that so unfortunate a timing will * occur as to allow simultaneous lock conditions to be established. * Finally, if some non-<tt>LockFile</tt> 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 by some uncooperative * process.<p> * * <li>If a Java security exception is thrown while testing for existence, * it is rethrown as a <tt>FileSecurityException</tt>. * * <li>Read the file's length. * * <li>If a Java security exception is thrown reading length, it is rethrown * as a <tt>FileSecurityException</tt> (it <em>is</em> possible somebody * concurrently refreshed the system Policy in the interim). * * <li>If the file does not have the expected length, a * <tt>WrongLengthException</tt> is thrown (we're trying to check * something that is not an HSQLDB lock file). * * <li>Open an input steam to read the file's <tt>MAGIC</tt> and heartbeat * timestamp values. * * <li>If a file not found exception is thrown above, it is rethrown as an * <tt>UnexpectedFileNotFoundException</tt> (we've already tested for * existence). * * <li>If a Java security exception is thrown above, it is rethrown as a * <tt>FileSecurityException</tt> (it <em>is</em> possible somebody * concurrently refreshed the system Policy in the interim). * * <li>Read the <tt>MAGIC</tt> value. * * <li>If an end of file exception is thrown above, it is rethrown as an * <tt>UnexpectedEndOfFileException</tt> (we've already tested the * length... did someone truncate the file in the interim?). * * <li>If an I/O exception is thrown, it is rethrown as an * <tt>UnexpectedFileIOException</tt> (we've already tested for * existence, length and successfully opened a stream...did someone, * for example, force unmount or physically remove the underlying device * in the interim?) * * <li>If the value read in does not match the expected <tt>MAGIC</tt> value, * a <tt>WrongMagicException</tt> is thrown (we're trying to check * something that is not an HSQLDB lock file). * * <li>Read the heartbeat timestamp. * * <li>If a Java security exception is thrown above, it is rethrown as a * <tt>FileSecurityException</tt> (it <em>is</em> possible somebody * concurrently refreshed the system Policy in the interim). * * <li>If an end of file execution is thrown above, it is rethrown as an * <tt>UnexpectedEndOfFileException</tt> (we've already tested the * length... did someone truncate the file in the interim?). * * <li>If an I/O exception is thrown, it is rethrown as an * <tt>UnexpectedFileIOException</tt> (we've already tested for * existence, length and successfully opened a stream...did someone, * for example, force unmount or physically remove the underlying device * in the interim?) * * <li>If the timestamp read in is less than or equal to * {@link #HEARTBEAT_INTERVAL_PADDED} milliseconds into the past or * future, then a <tt>LockHeldExternallyException</tt> is thrown. * * <li>Otherwise, this method simply returns. * </ol> * * @param withCreateNewFile if <tt>true</tt>, attempt to employ * <tt>File.createNewFile()</tt> as part of the check so as to * eliminate potential race conditions when establishing a new * lock file * @throws FileSecurityException if the check fails due to a Java * security permission check failure * @throws LockHeldExternallyException if it is determined that the * file's heartbeat timestamp is less than * <tt>HEARTBEAT_INTERVAL_PADDED</tt> into the past (or future) * @throws UnexpectedEndOfFileException if an <tt>EOFException</tt> is * thrown while reading either the magic or heartbeat timestamp values * @throws UnexpectedFileIOException if an <tt>IOException</tt> other than * <tt>EOFException</tt> is thrown while reading either the magic or * heartbeat timestamp values * @throws UnexpectedFileNotFoundException if a * <tt>FileNotFoundException</tt> is thrown while attempting to open a * stream to read the underlying file's magic and heartbeat timestamp * values * @throws WrongLengthException if it is determined that the length * of the file does not equal {@link #USED_REGION} * @throws WrongMagicException if it is determined that the file's * content does not start with {@link #MAGIC}. */
private final void checkHeartbeat(boolean withCreateNewFile) throws LockFile.FileSecurityException, LockFile.LockHeldExternallyException, LockFile.UnexpectedEndOfFileException, LockFile.UnexpectedFileIOException, LockFile.UnexpectedFileNotFoundException, LockFile.WrongLengthException, LockFile.WrongMagicException { long now; long lastHeartbeat; long length = 0; try { if (withCreateNewFile) { try { if (file.createNewFile()) { return; } } catch (IOException ioe) {} } if (!file.exists()) { return; } length = file.length(); } catch (SecurityException se) { throw new FileSecurityException(this, "checkHeartbeat", se); } if (length != USED_REGION) { if (length == 0) { file.delete(); return; } throw new WrongLengthException(this, "checkHeartbeat", length); } // Compute the current wall clock time *first* to reduce possibility // of unwanted time dilation effects introduced, for example, // by intervening thread or process context switches under CPU // bursts. // // Example: // // Say currentTimeMillis is actually somewhere in (-0.5 and 0.5] // and another LockFile concurrently writes a 0-valued heartbeat // timestamp. // // Then, if readHeartbeat comes first here, happens to 'win the race // condition' (reads the previous heartbeat: -10,000) and an intervening // switch causes greater than ~0.5 millisecond elapsed time to // be experienced between readHeartbeat and currentTimeMillis, then // currentTimeMillis will be computed as n (n > 0), and (now - // lastHearbeat) will be HEARTBEAT_INTERVAL + n, instead of // HEARTBEAT_INTERVAL. // // Now, let n be greater than (HEARTBEAT_INTERVAL_PADDED - // HEARTBEAT_INTERVAL). // // Then the check will succeed, although it should fail. // // On the other hand, if currentTimeMillis is computed first, the // worst than can happen is a false positive indication that // the read heartbeat timestamp value was written by a live LockFile // instance. // now = System.currentTimeMillis(); lastHeartbeat = readHeartbeat(); // Using padded interval to further reduce corner case effects, // now that heartbeat polling is in effect. // // Basically, it is absolutely essential to fail when a lock really is // still held elsewhere, so it is OK to fail on corner cases where // the last written heartbeat is very close to HEARTBEAT_INTERVAL // in the past and it is possible that timing jitters make it uncertain // whether the lock really is still held. if (Math.abs(now - lastHeartbeat) <= (HEARTBEAT_INTERVAL_PADDED)) { throw new LockHeldExternallyException(this, "checkHeartbeat", now, lastHeartbeat); } }
Closes this object's RandomAccessFile.

As a side-effect, the associated FileChannel object, if any, is closed as well.

Throws:
/** * Closes this object's {@link #raf RandomAccessFile}. <p> * * As a side-effect, the associated <tt>FileChannel</tt> object, if any, * is closed as well. * * @throws UnexpectedFileIOException if an <tt>IOException</tt> is thrown */
private final void closeRAF() throws LockFile.UnexpectedFileIOException { if (raf != null) { try { raf.close(); } catch (IOException ex) { throw new UnexpectedFileIOException(this, "closeRAF", ex); } finally { raf = null; } } }
Provides any optional locking actions for the tryLock() template method.

Descendants are free to provide additional functionality here, using the following rules:

PRE:

This method is called only from tryLock() and it is called if and only if tryLock() successfully invokes pollHeartbeat() and openRAF() first.

From this, it can be inferred that upon entry:

  1. locked == false.
  2. raf is a non-null instance that can be used to get a FileChannel instance, if desired.
  3. the underlying file either did not exist before invoking openRAF() or it was a valid but stale HSQLDB lock file because it:
    1. did exist,
    2. was readable on USED_REGION,
    3. had the expected length and MAGIC value and
    4. had a stale heartbeat timestamp value.

Further, it can be assumed that this object's heatbeat task is definitely cancelled and/or has never been scheduled at this point, so whatever timestamp is recorded in the lock file, if it did pre-exist, was written by a different LockFile instance or as the result of a previous, successful tryLock() invocation upon this LockFile instance.

Finally, it is important that this method does not rethrow any exceptions it encounters as unchecked exceptions to the calling context.

POST:

This method should return false if optional locking work is not performed or if it fails, else true.

In general, if optional locking work fails, then any resources acquired in the process should be freed before this method returns. In this way, the surrounding implementation can take advantage of a false return value to avoid calling doOptionalReleaseActions() as part of the tryRelease() method.

Note:

The default implementation does nothing and always returns false.

Returns:true if optional lock actions are performed and they succeed, else false
/** * Provides any optional locking actions for the {@link #tryLock() * tryLock()} template method. <p> * * Descendants are free to provide additional functionality here, * using the following rules: <p> * * <b>PRE:</b><p> * * This method is called only from <tt>tryLock()</tt> and it is called if * and only if <tt>tryLock()</tt> successfully invokes * <tt>pollHeartbeat()</tt> and <tt>openRAF()</tt> first. <p> * * From this, it can be inferred that upon entry: <p> * * <ol> * <li><tt>locked == false</tt>. * <li><tt>raf</tt> is a non-null instance that can be used to get a * <tt>FileChannel</tt> instance, if desired. * <li>the underlying file either did not exist before invoking * <tt>openRAF()</tt> or it was a valid but stale HSQLDB lock file * because it: * * <ol style="list-style-type: lower-roman"> * <li>did exist, * <li>was readable on <tt>USED_REGION</tt>, * <li>had the expected length and <tt>MAGIC</tt> value and * <li>had a stale heartbeat timestamp value. * </ol> * </ol> <p> * * Further, it can be assumed that this object's heatbeat task is definitely * cancelled and/or has never been scheduled at this point, so whatever * timestamp is recorded in the lock file, if it did pre-exist, was written * by a different <tt>LockFile</tt> instance or as the result of a previous, * successful <tt>tryLock()</tt> invocation upon this <tt>LockFile</tt> * instance. <p> * * Finally, it is important that this method does not rethrow any exceptions * it encounters as unchecked exceptions to the calling context. <p> * * <b>POST:</b><p> * * This method should return <tt>false</tt> if optional locking work is not * performed or if it fails, else <tt>true</tt>. <p> * * In general, if optional locking work fails, then any resources * acquired in the process should be freed before this method returns. * In this way, the surrounding implementation can take advantage of a * <tt>false</tt> return value to avoid calling {@link * #doOptionalReleaseActions() doOptionalReleaseActions()} as part of the * {@link #tryRelease() tryRelease()} method. <p> * * <b>Note:</b><p> * * The default implementation does nothing and always returns * <tt>false</tt>. <p> * * @return <tt>true</tt> if optional lock actions are performed and they * succeed, else <tt>false</tt> */
protected boolean doOptionalLockActions() { return false; }
Provides any optional release actions for the tryRelease() template method.

PRE:

It is important that this method does not rethrow any exceptions it encounters as unchecked exceptions to the calling context.

POST:

In general, false should be returned if optional locking work is not performed or if it fails, else true. However, the return value is currently treated as purely informative.

Note:

The default implementation does nothing and always returns false.

Returns:true if optional release actions are performed and they succeed, else false
/** * Provides any optional release actions for the {@link #tryRelease() * tryRelease()} template method. <p> * * <b>PRE:</b> <p> * * It is important that this method does not rethrow any exceptions * it encounters as unchecked exceptions to the calling context. <p> * * <b>POST:</b> <p> * * In general, <tt>false</tt> should be returned if optional locking work * is not performed or if it fails, else <tt>true</tt>. However, the return * value is currently treated as purely informative. <p> * * <b>Note:</b> <p> * * The default implementation does nothing and always returns false. <p> * * @return <tt>true</tt> if optional release actions are performed and they * succeed, else <tt>false</tt> */
protected boolean doOptionalReleaseActions() { return false; }
Initializes this object with a File object whose path has the canonical form of the given path argument.

PRE:

  1. This method is called once and only once per Lockfile instance.
  2. It is always the first method called after LockFile construction
  3. The supplied path argument is never null.
Params:
  • path – the abstract path representing the file this object is to use as its lock file
Throws:
/** * Initializes this object with a <tt>File</tt> object whose path has the * canonical form of the given <tt>path</tt> argument. <p> * * <b>PRE</b>:<p> * * <ol> * <li>This method is called once and <em>only</em> once per * <tt>Lockfile</tt> instance. * * <li>It is <em>always</em> the first method called after * <tt>LockFile</tt> construction * * <li>The supplied <tt>path</tt> argument is <em>never</em> * <tt>null</tt>. * </ol> * * @param path the abstract path representing the file this object is to * use as its lock file * @throws FileCanonicalizationException if an I/O error occurs upon * canonicalization of the given path, which is possible because * the given path may be illegal on the runtime file system or * because construction of the canonical pathname may require * native file system queries * @throws FileSecurityException if a required system property value cannot * be accessed, or if a Java security manager exists and its * <tt>{@link java.lang.SecurityManager#checkRead}</tt> method denies * read access to the file; or if its <tt>{@link * java.lang.SecurityManager#checkRead(java.lang.String)}</tt> * method does not permit verification of the existence of * all necessary parent directories; or if * its <tt>{@link * java.lang.SecurityManager#checkWrite(java.lang.String)}</tt> * method does not permit all necessary parent directories to be * created */
private final void setPath(String path) throws LockFile.FileCanonicalizationException, LockFile.FileSecurityException { // Should at least be absolutized for reporting purposes, just in case // a security or canonicalization exception gets thrown. path = FileUtil.getFileUtil().canonicalOrAbsolutePath(path); this.file = new File(path); try { FileUtil.getFileUtil().makeParentDirectories(this.file); } catch (SecurityException ex) { throw new FileSecurityException(this, "setPath", ex); } try { this.file = FileUtil.getFileUtil().canonicalFile(path); } catch (SecurityException ex) { throw new FileSecurityException(this, "setPath", ex); } catch (IOException ex) { throw new FileCanonicalizationException(this, "setPath", ex); } this.cpath = this.file.getPath(); }
Opens (constructs) this object's RandomAccessFile.

Throws:
/** * Opens (constructs) this object's {@link #raf RandomAccessFile}. <p> * * @throws UnexpectedFileNotFoundException if a * <tt>FileNotFoundException</tt> is thrown in response to * constructing the <tt>RandomAccessFile</tt> object. * @throws FileSecurityException if a required system property value cannot * be accessed, or if a Java security manager exists and its * <tt>{@link java.lang.SecurityManager#checkRead}</tt> method * denies read access to the file; or if its <tt>{@link * java.lang.SecurityManager#checkWrite(java.lang.String)}</tt> * method denies write access to the file */
private final void openRAF() throws LockFile.UnexpectedFileNotFoundException, LockFile.FileSecurityException, LockFile.UnexpectedFileIOException { try { raf = new RandomAccessFile(file, "rw"); } catch (SecurityException ex) { throw new FileSecurityException(this, "openRAF", ex); } catch (FileNotFoundException ex) { throw new UnexpectedFileNotFoundException(this, "openRAF", ex); } }
Checks whether the given DataInputStream contains the MAGIC value.
Params:
  • dis – the stream to check
Throws:
/** * Checks whether the given <tt>DataInputStream</tt> contains the * {@link #MAGIC} value. * * @param dis the stream to check * @throws FileSecurityException if a required system property value cannot * be accessed, or if a Java security manager exists and its * <tt>{@link java.lang.SecurityManager#checkRead}</tt> method * denies read access to the file * @throws UnexpectedEndOfFileException if an <tt>EOFException</tt> is * thrown while reading the <tt>DataInputStream</tt> * @throws UnexpectedFileIOException if an <tt>IOException</tt> other than * <tt>EOFException</tt> is thrown while reading the * <tt>DataInputStream</tt> * @throws WrongMagicException if a value other than <tt>MAGIC</tt> is read * from the <tt>DataInputStream</tt> */
private final void checkMagic(final DataInputStream dis) throws LockFile.FileSecurityException, LockFile.UnexpectedEndOfFileException, LockFile.UnexpectedFileIOException, LockFile.WrongMagicException { boolean success = true; final byte[] magic = new byte[MAGIC.length]; try { for (int i = 0; i < MAGIC.length; i++) { magic[i] = dis.readByte(); if (MAGIC[i] != magic[i]) { success = false; } } } catch (SecurityException ex) { throw new FileSecurityException(this, "checkMagic", ex); } catch (EOFException ex) { throw new UnexpectedEndOfFileException(this, "checkMagic", ex); } catch (IOException ex) { throw new UnexpectedFileIOException(this, "checkMagic", ex); } if (!success) { throw new WrongMagicException(this, "checkMagic", magic); } }
Retrieves the last written hearbeat timestamp from this object's lock file. If this object's lock file does not exist, then Long.MIN_VALUE (the earliest time representable as a long in Java) is returned immediately.

Throws:
Returns:the hearbeat timestamp read from this object's lock file, as a long 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. If this object's lock file does not exist, then <tt>Long.MIN_VALUE * </tt> (the earliest time representable as a <tt>long</tt> in Java) is * returned immediately. <p> * * @return the hearbeat timestamp read from this object's lock file, * as a <tt>long</tt> value or, if this object's lock * file does not exist, <tt>Long.MIN_VALUE</tt>, the earliest time * representable as a <tt>long</tt> in Java. * @throws FileSecurityException if a required system property value cannot * be accessed, or if a Java security manager exists and its * <tt>{@link java.lang.SecurityManager#checkRead}</tt> method * denies read access to the file * @throws UnexpectedEndOfFileException if an <tt>EOFException</tt> is * thrown while attempting to read the target file's <tt>MAGIC</tt> * or heartbeat timestamp value * @throws UnexpectedFileNotFoundException if, after successfully testing * for existence, the target file is not found a moment later while * attempting to read its <tt>MAGIC</tt> and heartbeat timestamp * values * @throws UnexpectedFileIOException if any other input stream error occurs * @throws WrongMagicException if the lock file does not start with the * the {@link #MAGIC} value */
private final long readHeartbeat() throws LockFile.FileSecurityException, LockFile.UnexpectedFileNotFoundException, LockFile.UnexpectedEndOfFileException, LockFile.UnexpectedFileIOException, LockFile.WrongMagicException { FileInputStream fis = null; DataInputStream dis = null; try { if (!file.exists()) { return Long.MIN_VALUE; } fis = new FileInputStream(file); dis = new DataInputStream(fis); checkMagic(dis); return dis.readLong(); } catch (SecurityException ex) { throw new FileSecurityException(this, "readHeartbeat", ex); } catch (FileNotFoundException ex) { throw new UnexpectedFileNotFoundException(this, "readHeartbeat", ex); } catch (EOFException ex) { throw new UnexpectedEndOfFileException(this, "readHeartbeat", ex); } catch (IOException ex) { throw new UnexpectedFileIOException(this, "readHeartbeat", ex); } finally { if (fis != null) { try { fis.close(); } catch (IOException ioe) { // ioe.printStackTrace(); } } } }
Schedules the lock heartbeat task.
/** * Schedules the lock heartbeat task. */
private final void startHeartbeat() { if (timerTask == null || HsqlTimer.isCancelled(timerTask)) { Runnable runner = new HeartbeatRunner(); timerTask = timer.schedulePeriodicallyAfter(0, HEARTBEAT_INTERVAL, runner, true); } }
Cancels the lock heartbeat task.
/** * Cancels the lock heartbeat task. */
private final void stopHeartbeat() { if (timerTask != null && !HsqlTimer.isCancelled(timerTask)) { HsqlTimer.cancel(timerTask); timerTask = null; } }
Writes the MAGIC value to this object's lock file that distinguishes it as an HSQLDB lock file.

Throws:
  • FileSecurityException – possibly never (seek and write are native methods whose JavaDoc entries do not actually specify throwing SecurityException). However, it is conceivable that these native methods may, in turn, access Java methods that do throw SecurityException. In this case, a SecurityException might be thrown if a required system property value cannot be accessed, or if a security manager exists and its SecurityManager.checkWrite(FileDescriptor) method denies write access to the file
  • UnexpectedEndOfFileException – if an end of file exception is thrown while attempting to write the MAGIC value to the target file (typically, this cannot happen, but the case is included to distinguish it from the general IOException case).
  • UnexpectedFileIOException – if any other I/O error occurs while attempting to write the MAGIC value to the target file.
/** * Writes the {@link #MAGIC} value to this object's lock file that * distinguishes it as an HSQLDB lock file. <p> * * @throws FileSecurityException possibly never (seek and write are native * methods whose JavaDoc entries do not actually specify throwing * <tt>SecurityException</tt>). However, it is conceivable that these * native methods may, in turn, access Java methods that do * throw <tt>SecurityException</tt>. In this case, a * <tt>SecurityException</tt> might be thrown if a required system * property value cannot be accessed, or if a security manager exists * and its <tt>{@link * java.lang.SecurityManager#checkWrite(java.io.FileDescriptor)}</tt> * method denies write access to the file * @throws UnexpectedEndOfFileException if an end of file exception is * thrown while attempting to write the <tt>MAGIC</tt> value to the * target file (typically, this cannot happen, but the case is * included to distinguish it from the general <tt>IOException</tt> * case). * @throws UnexpectedFileIOException if any other I/O error occurs while * attempting to write the <tt>MAGIC</tt> value to the target file. */
private final void writeMagic() throws LockFile.FileSecurityException, LockFile.UnexpectedEndOfFileException, LockFile.UnexpectedFileIOException { try { raf.seek(0); raf.write(MAGIC); } catch (SecurityException ex) { throw new FileSecurityException(this, "writeMagic", ex); } catch (EOFException ex) { throw new UnexpectedEndOfFileException(this, "writeMagic", ex); } catch (IOException ex) { throw new UnexpectedFileIOException(this, "writeMagic", ex); } }
Writes the current hearbeat timestamp value to this object's lock file.

Throws:
  • FileSecurityException – possibly never (seek and write are native methods whose JavaDoc entries do not actually specify throwing SecurityException). However, it is conceivable that these native methods may, in turn, access Java methods that do throw SecurityException. In this case, a SecurityException might be thrown if a required system property value cannot be accessed, or if a security manager exists and its SecurityManager.checkWrite(FileDescriptor) method denies write access to the file
  • UnexpectedEndOfFileException – if an end of file exception is thrown while attempting to write the heartbeat timestamp value to the target file (typically, this cannot happen, but the case is included to distinguish it from the general IOException case).
  • UnexpectedFileIOException – if the current heartbeat timestamp value cannot be written due to an underlying I/O error
/** * Writes the current hearbeat timestamp value to this object's lock * file. <p> * * @throws FileSecurityException possibly never (seek and write are native * methods whose JavaDoc entries do not actually specify throwing * <tt>SecurityException</tt>). However, it is conceivable that these * native methods may, in turn, access Java methods that do throw * <tt>SecurityException</tt>. In this case, a * <tt>SecurityException</tt> might be thrown if a required system * property value cannot be accessed, or if a security manager exists * and its <tt>{@link * java.lang.SecurityManager#checkWrite(java.io.FileDescriptor)}</tt> * method denies write access to the file * @throws UnexpectedEndOfFileException if an end of file exception is * thrown while attempting to write the heartbeat timestamp value to * the target file (typically, this cannot happen, but the case is * included to distinguish it from the general IOException case). * @throws UnexpectedFileIOException if the current heartbeat timestamp * value cannot be written due to an underlying I/O error */
private final void writeHeartbeat() throws LockFile.FileSecurityException, LockFile.UnexpectedEndOfFileException, LockFile.UnexpectedFileIOException { try { raf.seek(MAGIC.length); raf.writeLong(System.currentTimeMillis()); } catch (SecurityException ex) { throw new FileSecurityException(this, "writeHeartbeat", ex); } catch (EOFException ex) { throw new UnexpectedEndOfFileException(this, "writeHeartbeat", ex); } catch (IOException ex) { throw new UnexpectedFileIOException(this, "writeHeartbeat", ex); } }
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);
yields true.

Note that file must be a canonical reference to correctly satisfy this contract.

Params:
  • obj – the reference object with which to compare.
See Also:
Returns:true if this object is equal to the obj argument; false otherwise.
/** * Tests whether some other object is "equal to" this one. <p> * * An object is considered equal to a <tt>LockFile</tt> object if and * only if it is not null, it is an instance of <tt>LockFile</tt> 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 <tt>LockFile</tt>, and the expression: <p> * * <pre> * this == other || * this.file == null ? other.file == null : this.file.equals(other.file); * </pre> * * yields true. <p> * * Note that <tt>file</tt> must be a canonical reference to correctly * satisfy this contract. <p> * * @param obj the reference object with which to compare. * @return <tt>true</tt> if this object is equal to the <tt>obj</tt> * argument; <tt>false</tt> otherwise. * @see #hashCode */
public final boolean equals(final Object obj) { if (this == obj) { return true; } else if (obj instanceof LockFile) { LockFile other = (LockFile) obj; return (this.file == null) ? other.file == null : this.file.equals(other.file); } return false; }
Retrieves the canonical path of this object's lock file, as a String object.

Returns:the canonical path of this object's lock file.
/** * Retrieves the canonical path of this object's lock file, as a * <tt>String</tt> object. <p> * * @return the canonical path of this object's lock file. */
public final String getCanonicalPath() { return cpath; }
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.

See Also:
Returns:a hash code value for this object.
/** * Retrieves the hash code value for this object. <p> * * The value is zero if <tt>file</tt> is <tt>null</tt>, else the * <tt>hashCode</tt> of <tt>file</tt>. That is, two <tt>LockFile</tt> * objects have the same <tt>hashCode</tt> value if they refer to the * same lock file. <p> * * Note that <tt>file</tt> must be a canonical reference to correctly * satisfy this contract. <p> * * @return a hash code value for this object. * @see #equals(java.lang.Object) */
public final int hashCode() { return file == null ? 0 : file.hashCode(); }
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 restrictions 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 (especially 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.

See Also:
Returns:true if this object has successfully obtained and is still holding (has not yet released) a lock condition, else false
/** * Retrieves whether this object has successfully obtained and is still * holding (has not yet released) a cooperative lock condition on its * lock file. <p> * * <b>Note:</b> <p> * * Due to platform-independence restrictions 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. <p> * * For instance, under JVMs with no <tt>java.nio</tt> package or under * operating systems that do not apply mandatory file locking (especially * 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. <p> * * Because of this, the <tt>isValid()</tt> method is provided in the public * interface in order to allow clients to detect at least a subset of such * situations. <p> * * @return <tt>true</tt> if this object has successfully obtained and is * still holding (has not yet released) a lock condition, else * <tt>false</tt> * @see #isValid */
public final boolean isLocked() { return locked; }
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.
Params:
  • 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
/** * 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. * * @param path the path to test * @return <tt>true</tt> if there is currently something preventing the * acquisition of a cooperative lock condition using the specified * <tt>path</tt>, else <tt>false</tt> */
public static boolean isLocked(final String path) { boolean locked = true; try { LockFile lockFile = LockFile.newLockFile(path); lockFile.checkHeartbeat(false); locked = false; } catch (Exception e) {} return locked; }
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
Throws:
  • 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;
Returns:true if this object holds a valid lock condition on its lock file; else false
/** * Retrieves whether this object holds a valid lock condition on its * lock file. <p> * * More formally, this method retrieves true if and only if: <p> * * <pre> * isLocked() && file != null && file.exists() && raf != null * </pre> * * @return <tt>true</tt> if this object holds a valid lock condition on its * lock file; else <tt>false</tt> * @throws SecurityException if a required system property value cannot * be accessed, or if a Java security manager exists and its * <tt>checkRead</tt> method denies read access to the lock file; */
public boolean isValid() { return isLocked() && file != null && file.exists() && raf != null; }
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() +
"]";
Throws:
See Also:
Returns:a String representation of this object.
/** * Retrieves a String representation of this object. <p> * * The String is of the form: <p> * * <pre> * super.toString() + * "[file=" + getCanonicalPath() + * ", exists=" + file.exists() + * ", locked=" + isLocked() + * ", valid=" + isValid() + * ", " + toStringImpl() + * "]"; * </pre> * * * @return a String representation of this object. * @see #toStringImpl * @throws SecurityException if a required system property value cannot * be accessed, or if a security manager exists and its <tt>{@link * java.lang.SecurityManager#checkRead}</tt> method denies * read access to the lock file; */
public String toString() { return new StringBuilder(super.toString()).append("[file =").append( cpath).append(", exists=").append(file.exists()).append( ", locked=").append(isLocked()).append(", valid=").append( isValid()).append(", ").append(toStringImpl()).append( "]").toString(); }
Retrieves an implementation-specific tail value for the toString() method.

The default implementation returns the empty string.

See Also:
Returns:an implementation-specific tail value for the toString() method
/** * Retrieves an implementation-specific tail value for the * <tt>toString()</tt> method. <p> * * The default implementation returns the empty string. * * @return an implementation-specific tail value for the <tt>toString()</tt> * method * @see #toString */
protected String toStringImpl() { return ""; }
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.
/** * Retrieves the number of times <tt>checkHeartbeat</tt> may fail before * <tt>pollHeartbeat</tt> fails as a consequence. <p> * * The value is obtained in the following manner: <p> * * <ol> * <li>retries is assigned <tt>POLL_RETRIES_DEFAULT</tt>. * * <li>retries is assigned <tt>Integer.getInteger(POLL_RETRIES_PROPERTY, * retries)</tt> inside a try-catch block to silently ignore any security * exception. * * <li>If retries is less than one (1), retries is assigned one (1). * </ol> * * @return the number of times <tt>checkHeartbeat</tt> may fail before * <tt>pollHeartbeat</tt> fails as a consequence. */
public int getPollHeartbeatRetries() { int retries = POLL_RETRIES_DEFAULT; try { retries = Integer.getInteger( HsqlDatabaseProperties.system_lockfile_poll_retries_property, retries).intValue(); } catch (Exception e) {} if (retries < 1) { retries = 1; } return retries; }
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
/** * Retrieves the interval, in milliseconds, that <tt>pollHeartbeat</tt> * waits between failed invocations of <tt>checkHeartbeat</tt>. * * The value is obtained in the following manner: <p> * * <ol> * <li>interval is assigned <tt>10 + (HEARTBEAT_INTERVAL_PADDED * getPollHeartbeatRetries())</tt> * * <li>interval is assigned <tt>Long.getLong(POLL_INTERVAL_PROPERTY, * interval)</tt>, inside a try-catch block, to silently ignore any security * exception. * * <li>If interval is less than or equal to zero (0), interval is reassigned * <tt>10 + (HEARTBEAT_INTERVAL_PADDED / getPollHeartbeatRetries())</tt> * </ol> * * @return the interval, in milliseconds, that <tt>pollHeartbeat</tt> * waits between failed invocations of <tt>checkHeartbeat</tt> */
public long getPollHeartbeatInterval() { int retries = getPollHeartbeatRetries(); long interval = 10 + (HEARTBEAT_INTERVAL_PADDED / retries); try { interval = Long.getLong(POLL_INTERVAL_PROPERTY, interval).longValue(); } catch (Exception e) {} if (interval <= 0) { interval = 10 + (HEARTBEAT_INTERVAL_PADDED / retries); } return interval; }
Polls the underlying lock file to determine if a lock condition exists.

Specifically, polls checkHeartbeat at the configured interval until the check passes, the current poll interval wait state is interrupted or the configured number of poll retries is reached.

The last exception thrown by checkHeartbeat is re-thrown if no check passes.

Throws:
/** * Polls the underlying lock file to determine if a lock condition * exists. <p> * * Specifically, polls {@link #checkHeartbeat(boolean) checkHeartbeat} at * the configured interval until the check passes, the current poll interval * wait state is interrupted or the configured number of poll retries is * reached. <p> * * The last exception thrown by <tt>checkHeartbeat</tt> is re-thrown if no * check passes. <p> * * @throws FileSecurityException if the Java security system denied read * to the target file * @throws LockHeldExternallyException if the target file's heartbeat * timestamp indicated that a lock condition was held by another * <tt>LockFile</tt>. * @throws UnexpectedFileNotFoundException if the target file became * unavailable between a test for existence and an attempt to read * the <tt>MAGIC</tt> or heartbeat timestamp value. * @throws UnexpectedEndOfFileException if an <tt>EOFException</tt> was * raised while trying to read the <tt>MAGIC</tt> or heartbeat * timestamp value of the target file * @throws UnexpectedFileIOException if an <tt>EOFException</tt> other than * <tt>EOFException</tt> was raised while trying to read the * <tt>MAGIC</tt> or heartbeat timestamp value of the target file * @throws WrongLengthException if the target file did not have the * expected length * @throws WrongMagicException if the target file did not begin with the * expected <tt>MAGIC</tt> value */
private final void pollHeartbeat() throws LockFile.FileSecurityException, LockFile.LockHeldExternallyException, LockFile.UnexpectedFileNotFoundException, LockFile.UnexpectedEndOfFileException, LockFile.UnexpectedFileIOException, LockFile.WrongLengthException, LockFile.WrongMagicException { boolean success = false; int retries = getPollHeartbeatRetries(); long interval = getPollHeartbeatInterval(); LockFile.BaseException reason = null; for (int i = retries; i > 0; i--) { try { checkHeartbeat(true); // withCreateNewFile == true success = true; break; } catch (LockFile.BaseException ex) { reason = ex; } // We get here if and only if success == false and reason != null, // so its OK to 'break' try { Thread.sleep(interval); } catch (InterruptedException ex) { break; } } /** * @todo: * Do not want to specify just BaseException in the throws clause. * Is this really the cleanest way? */ if (!success) { if (reason instanceof FileSecurityException) { throw (FileSecurityException) reason; } else if (reason instanceof LockHeldExternallyException) { throw (LockHeldExternallyException) reason; } else if (reason instanceof UnexpectedFileNotFoundException) { throw (UnexpectedFileNotFoundException) reason; } else if (reason instanceof UnexpectedEndOfFileException) { throw (UnexpectedEndOfFileException) reason; } else if (reason instanceof UnexpectedFileIOException) { throw (UnexpectedFileIOException) reason; } else if (reason instanceof WrongLengthException) { throw (WrongLengthException) reason; } else if (reason instanceof WrongMagicException) { throw (WrongMagicException) reason; } } }
Attempts to obtain a cooperative lock condition upon this object's lock file.

Throws:
  • FileSecurityException – if the lock condition could not be obtained due to a Java security permission violation
  • 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.
  • 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.
  • 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 file
  • UnexpectedFileIOException – if the lock condition could not be obtained due to an IOException other than EOFException
  • WrongLengthException – if the lock condition could not be obtained because the target file was the wrong length
  • WrongMagicException – if the lock condition could not be obtained because the target file had the wrong MAGIC value
Returns:true if this object already holds a lock or the lock was obtained successfully, else false
Returns:true if and only if a lock condition is obtained; false otherwise. In general, an exception will always be thrown if a lock condition cannot be obtained for any reason
/** * Attempts to obtain a cooperative lock condition upon this object's lock * file. <p> * * @return <tt>true</tt> if this object already holds a lock or the lock was * obtained successfully, else <tt>false</tt> * @throws FileSecurityException if the lock condition could not be * obtained due to a Java security permission violation * @throws 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 <tt>LockFile</tt>. * @throws 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 * <tt>MAGIC</tt> or heartbeat timestamp value. * @throws UnexpectedEndOfFileException if the lock condition could not be * obtained because <tt>EOFException</tt> was raised while trying to * read the <tt>MAGIC</tt> or heartbeat timestamp value of the target * file * @throws UnexpectedFileIOException if the lock condition could not be * obtained due to an <tt>IOException</tt> other than * <tt>EOFException</tt> * @throws WrongLengthException if the lock condition could not be obtained * because the target file was the wrong length * @throws WrongMagicException if the lock condition could not be obtained * because the target file had the wrong <tt>MAGIC</tt> value * @return <tt>true</tt> if and only if a lock condition is obtained; * <tt>false</tt> otherwise. In general, an exception will * <em>always</em> be thrown if a lock condition cannot be obtained for * any reason */
public final boolean tryLock() throws LockFile.FileSecurityException, LockFile.LockHeldExternallyException, LockFile.UnexpectedFileNotFoundException, LockFile.UnexpectedEndOfFileException, LockFile.UnexpectedFileIOException, LockFile.WrongLengthException, LockFile.WrongMagicException { if (this.locked) { return true; } try { pollHeartbeat(); openRAF(); // Must come *after* openRAF to comply with the // doOptionalLockActions() PRE: assertion contract. // // <sigh> In an ideal world, it would be possible from Java to open // a file handle and obtain at least one associated NIO FileLock in // one kernel-enforced atomic operation. However, we can't even // guarantee that NIO is available. // // Note: // The NIOLockFile version of this operation is 'self cleaning'... // if it fails for some reason, then it does a 'best effort' to // eagerly release and nullify its FileLock object before // returning. doOptionalLockActions(); // Inlined the following to reduce potential for timing issues // such as initial timer thread startup induced delay of first // pulse. // // In general, what we'll get is two initial pulses in rapid // succession: one here and one an instant later as a result of // startHeartbeat (which is OK... no harm, and it's one-shot // behaviour, not repeated on every writeHeartbeat) // // Unfortunately, we may occasionally encounter astronomic (at least // in computer time) delays between invocation of startHeartbeat // and the time at which effort is actually expended toward writing // the initial MAGIC and heartbeat timestamp values. // // Another good reason to inline the first writeHeartbeat is to // provide a last line of defence against inter-process as well // as inter-thread race conditions. That is, exceptions thrown in // HeartbeatRunner.run() do yet get propagated anywhere useful. // // Of course, if we are operating under a fully-featured and correct // NIO implementation, the concerns described above are really // non-issues... at this point, we will have (at least in theory) a // valid OS-enforced file lock. // // But in an ideal world (with or without NIO), any pulse failure in // HeartbeatRunner.run() would flag the database Logger that a // database lock condition violation has occurred, preventing further // ad-hoc operation of the database. // // The problem is, if a lock condition has been violated that is // being used by a database instance, what mechanism can be used to // safely checkpoint, backup and/or shut down that instance? For // all we know, the violation indicates that another instance is now // happily writing to the other database files... // // A prudent course of action to take under detection of a // cooperative lock condition violation in the heartbeatRunner task // would be to perform a 'SCRIPT <file>' to some pre-ordained 'safe' // backup location using a globally unique file name and then do a // 'SHUTDOWN IMMEDIATELY' in one database-scope atomic context (e.g. // a single JDBC statement execution). // // However, by the time a lock condition violation has been detected, // the data cache file (and log/script) may already be quite // corrupted, meaning the resulting script may be totally inaccurate // or worse. // // Bottom line: // // Regardless of this inlining measure, if a lock violation occurs // after startHeartbeat, it's almost certain there's much worse in // store... writeMagic(); writeHeartbeat(); FileUtil.getFileUtil().deleteOnExit(file); this.locked = true; startHeartbeat(); } finally { if (!locked) { // No harm in this... // // If this LockFile is an NIOLockFile instance and // doOptionalLockActions() failed above, then a 'best // effort' optional release was already performed and // this will be a no-op. // // On the other hand, if doOptionalLockActions() succeeded, best // to undo them here right away, since the core locking work // failed. // // In practice, however, it is very unlikely for the core // locking work to fail if this LockFile is an NIOLockFile // instance and doOptionalLockActions() succeeded, except // under JVM implementations whose NIO package is broken in // a very specific way. // // Other possibilities include unfortunate timing of events // under certain network file system or removable media // configurations, device umounts, physical removal of storage // media, Java security or file system security policy // updates, etc. doOptionalReleaseActions(); try { closeRAF(); } catch (Exception ex) { // It's too late to do anything useful with this exception. // // we've already/ failed and will let the caller know the // reason via the exception thrown in the try block. // // ex.printStackTrace(); } } } return this.locked; }
Attempts to release any cooperative lock condition this object may hold upon its lock file.

Throws:
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. <p> * * * @return <tt>true</tt> if this object does not currently hold a * lock condition or the lock is released completely (including * successful file deletion), else <tt>false</tt>. * @throws FileSecurityException if a <tt>SecurityException</tt> is raised * in the process of releasing the lock condition * @throws UnexpectedFileIOException if an IoException is raised in the * process of releasing the lock condition */
public final boolean tryRelease() throws LockFile.FileSecurityException, LockFile.UnexpectedFileIOException { boolean released = !locked; if (released) { return true; } stopHeartbeat(); doOptionalReleaseActions(); UnexpectedFileIOException closeRAFReason = null; FileSecurityException securityReason = null; try { try { closeRAF(); } catch (UnexpectedFileIOException ex) { closeRAFReason = ex; } try { // Hack Alert: // // Even without the presence of concurrent locking attempts, // the delete or exists invocations below occasionally return // false otherwise, perhaps due to a race condition with the // heartbeat timestamp writer task or some nio file lock release // timing issue? // // TODO: // // determine if this is an external constraint or if we can // solve it instead by waiting for any in-progress // writeHeartbeat operation to conclude. Thread.sleep(100); } catch (Exception ex) { // ex.printStackTrace(); } try { released = file.delete(); // Perhaps excessive... // // Another Lockfile may recreate the file an instant after it is // deleted above (if it it deleted successfully, that is) // released = !file.exists(); } catch (SecurityException ex) { securityReason = new FileSecurityException(this, "tryRelease", ex); } } finally { // Regardless of whether all release work succeeds, it is important // to indicate that, from the perspective of this instance, a lock // condition is no longer held. // // However, in a world of concurrent execution, we do not want to // to expose this fact externally until *after* all release work has // been at least attempted. this.locked = false; } if (closeRAFReason != null) { throw closeRAFReason; } else if (securityReason != null) { throw securityReason; } return released; }
For internal use only.

This Runnable class provides the implementation for the timed task that periodically writes out a heartbeat timestamp to the lock file.

/** * For internal use only. <p> * * This Runnable class provides the implementation for the timed task * that periodically writes out a heartbeat timestamp to the lock file.<p> */
private final class HeartbeatRunner implements Runnable { public void run() { try { LockFile.this.writeHeartbeat(); } catch (Throwable t) { Error.printSystemOut(t.toString()); } } }
Base exception class for lock condition specific exceptions.

/** * Base exception class for lock condition specific exceptions. <p> * */
public abstract static class BaseException extends Exception { private final LockFile lockFile; private final String inMethod;
Constructs a new LockFile.BaseException.

Params:
  • lockFile – the underlying LockFile object
  • inMethod – the name of the method in which the exception was originally thrown (may be passed up several levels)
/** * Constructs a new <tt>LockFile.BaseException</tt>. <p> * * @param lockFile the underlying <tt>LockFile</tt> object * @param inMethod the name of the method in which the exception * was originally thrown (may be passed up several levels) */
public BaseException(final LockFile lockFile, final String inMethod) { super(); if (lockFile == null) { throw new NullPointerException("lockFile"); } if (inMethod == null) { throw new NullPointerException("inMethod"); } this.lockFile = lockFile; this.inMethod = inMethod; }
Subclass-specific override.

Returns:representation of lockFile and inMethod, as String object
/** * Subclass-specific override. <p> * * @return representation of <tt>lockFile</tt> and * <tt>inMethod</tt>, as <tt>String</tt> object */
public String getMessage() { // override return "lockFile: " + lockFile + " method: " + inMethod; }
Getter for inMethod property.

Returns:name of method in which exception originally occurred
/** * Getter for <tt>inMethod</tt> property. <p> * * @return name of method in which exception originally occurred */
public String getInMethod() { return this.inMethod; }
Getter for lockFile property.

Returns:the underlying LockFile object
/** * Getter for <tt>lockFile</tt> property. <p> * * @return the underlying <tt>LockFile</tt> object */
public LockFile getLockFile() { return this.lockFile; } }
Thrown when canonicalization of a LockFile object's target file path fails.

This is possible because the given path may be illegal on the runtime file system or because construction of the canonical pathname may require filesystem queries.

/** * Thrown when canonicalization of a <tt>LockFile</tt> object's target * file path fails. <p> * * This is possible because the given path may be illegal on the runtime * file system or because construction of the canonical pathname may require * filesystem queries. */
public static final class FileCanonicalizationException extends BaseException { private final IOException reason;
Constructs a new FileCanonicalizationException.

Params:
  • lockFile – the underlying LockFile object
  • inMethod – the name of the method in which the exception was originally thrown (may be passed up several levels)
  • reason – the exception thrown during canonicalization
/** * Constructs a new <tt>FileCanonicalizationException</tt>.<p> * * @param lockFile the underlying <tt>LockFile</tt> object * @param inMethod the name of the method in which the exception * was originally thrown (may be passed up several levels) * @param reason the exception thrown during canonicalization */
public FileCanonicalizationException(final LockFile lockFile, final String inMethod, final IOException reason) { super(lockFile, inMethod); this.reason = reason; }
Retrieves the underlying IOException.

Returns:Value of property reason.
/** * Retrieves the underlying <tt>IOException</tt>. <p> * * @return Value of property reason. */
public IOException getReason() { return this.reason; }
Subclass-specific override.

Returns:representation of lockFile, inMethod and reason, as a String object
/** * Subclass-specific override. <p> * * @return representation of <tt>lockFile</tt>, <tt>inMethod</tt> and * <tt>reason</tt>, as a <tt>String</tt> object */
public String getMessage() { // override return super.getMessage() + " reason: " + reason; } }
Thrown when access to a LockFile object's target file raises a Java SecurityException.

This can occur if a required system property value cannot be accessed, or if a security manager exists and its SecurityManager.checkRead method denies read access to a file; or if its SecurityManager.checkRead(String) method does not permit verification of the existence of all necessary parent directories; or if its SecurityManager.checkWrite(String) method does not permit all necessary parent directories to be created.

/** * Thrown when access to a <tt>LockFile</tt> object's target file raises a * Java <tt>SecurityException</tt>. <p> * * This can occur if a required system property value cannot be accessed, or * if a security manager exists and its <tt>{@link * java.lang.SecurityManager#checkRead}</tt> method denies read access to a * file; or if its <tt>{@link * java.lang.SecurityManager#checkRead(java.lang.String)}</tt> * method does not permit verification of the existence of all necessary * parent directories; or if its <tt>{@link * java.lang.SecurityManager#checkWrite(java.lang.String)}</tt> * method does not permit all necessary parent directories to be * created. <p> * */
public static final class FileSecurityException extends BaseException { private final SecurityException reason;
Constructs a new FileSecurityException.

Params:
  • lockFile – the underlying LockFile object
  • inMethod – the name of the method in which the exception was originally thrown (may be passed up several levels)
  • reason – the underlying Java security exception
/** * Constructs a new <tt>FileSecurityException</tt>. <p> * * @param lockFile the underlying LockFile object * @param inMethod the name of the method in which the exception * was originally thrown (may be passed up several levels) * @param reason the underlying Java security exception */
public FileSecurityException(final LockFile lockFile, final String inMethod, final SecurityException reason) { super(lockFile, inMethod); this.reason = reason; }
Retrieves the underlying SecurityException.

Returns:Value of property reason.
/** * Retrieves the underlying <tt>SecurityException</tt>. <p> * * @return Value of property reason. */
public SecurityException getReason() { return this.reason; }
Subclass-specific override.
Returns:representation of lockFile, inMethod and reason, as a String object
/** * Subclass-specific override. * * @return representation of lockFile, inMethod and reason, as * a String object */
public String getMessage() { // override return super.getMessage() + " reason: " + reason; } }
Thrown when an externally held lock condition prevents lock acquisition.

Specifically, this exception is thrown when polling fails because the lock file's heartbeat timestamp value indicates that another LockFile object still holds the lock condition.

/** * Thrown when an externally held lock condition prevents lock * acquisition. <p> * * Specifically, this exception is thrown when polling fails because the * lock file's heartbeat timestamp value indicates that another LockFile * object still holds the lock condition. <p> * */
public static final class LockHeldExternallyException extends BaseException { private final long read; private final long heartbeat;
Constructs a new LockHeldExternallyException.

Params:
  • lockFile – the underlying LockFile object
  • inMethod – the name of the method in which the exception was originally thrown (may be passed up several levels)
  • read – the time, in milliseconds since 1970-01-01, at which the heartbeat timestamp value was read from the lock file
  • heartbeat – the heartbeat timestamp value, in milliseconds since 1970-01-01, that was read from the lock file.
/** * Constructs a new <tt>LockHeldExternallyException</tt>. <p> * * @param lockFile the underlying <tt>LockFile</tt> object * @param inMethod the name of the method in which the exception * was originally thrown (may be passed up several levels) * @param read the time, in milliseconds since 1970-01-01, at which * the heartbeat timestamp value was read from the lock file * @param heartbeat the heartbeat timestamp value, in milliseconds * since 1970-01-01, that was read from the lock file. */
public LockHeldExternallyException(final LockFile lockFile, final String inMethod, final long read, final long heartbeat) { super(lockFile, inMethod); this.read = read; this.heartbeat = heartbeat; }
Getter for the heartbeat attribute.

Returns:the heartbeat timestamp value, in milliseconds since 1970-01-01, that was read from the lock file.
/** * Getter for the <tt>heartbeat</tt> attribute. <p> * * @return the heartbeat timestamp value, in milliseconds since * 1970-01-01, that was read from the lock file. */
public long getHeartbeat() { return this.heartbeat; }
Getter for the read attribute.

Returns:the time, in milliseconds since 1970-01-01, that the heartbeat timestamp value was read from the lock file.
/** * Getter for the <tt>read</tt> attribute. <p> * * @return the time, in milliseconds since 1970-01-01, that * the heartbeat timestamp value was read from the lock file. */
public long getRead() { return this.read; }
Subclass-specific override.

Returns:representation of lockFile, inMethod, read and heartbeat, as a String object
/** * Subclass-specific override. <p> * * @return representation of <tt>lockFile</tt>, <tt>inMethod</tt>, * <tt>read</tt> and <tt>heartbeat</tt>, as a <tt>String</tt> * object */
public String getMessage() { // override return super.getMessage() + " read: " + HsqlDateTime.getTimestampString(this.read) + " heartbeat - read: " + (this.heartbeat - this.read) + " ms."; } }
Thrown when access to a LockFile object's target file raises an unexpected EOFException.
/** * Thrown when access to a <tt>LockFile</tt> object's target file raises an * unexpected <tt>EOFException</tt>. */
public static final class UnexpectedEndOfFileException extends BaseException { private final EOFException reason;
Constructs a new UnexpectedEndOfFileException.

Params:
  • lockFile – the underlying LockFile object
  • inMethod – the name of the method in which the exception was originally thrown (may be passed up several levels)
  • reason – the underlying exception
/** * Constructs a new <tt>UnexpectedEndOfFileException</tt>. <p> * * @param lockFile the underlying <tt>LockFile</tt> object * @param inMethod the name of the method in which the exception * was originally thrown (may be passed up several levels) * @param reason the underlying exception */
public UnexpectedEndOfFileException(final LockFile lockFile, final String inMethod, final EOFException reason) { super(lockFile, inMethod); this.reason = reason; }
Retrieves the underlying EOFException.

Returns:Value of property reason.
/** * Retrieves the underlying <tt>EOFException<tt>. <p> * * @return Value of property reason. */
public EOFException getReason() { return this.reason; }
Subclass-specific override.

Returns:representation of lockFile, inMethod and reason, as a String object
/** * Subclass-specific override. <p> * * @return representation of <tt>lockFile<tt>, <tt>inMethod</tt> and * <tt>reason</tt>, as a <tt>String</tt> object */
public String getMessage() { // override return super.getMessage() + " reason: " + reason; } }
Thrown when access to a LockFile object's target file raises an unexpected IOException other than EOFException.
/** * Thrown when access to a <tt>LockFile</tt> object's target file raises an * unexpected <tt>IOException</tt> other than <tt>EOFException</tt>. */
public static final class UnexpectedFileIOException extends BaseException { private final IOException reason;
Constructs a new UnexpectedFileIOException.
Params:
  • lockFile – the underlying LockFile object
  • inMethod – the name of the method in which the exception was originally thrown (may be passed up several levels)
  • reason – the underlying exception
/** * Constructs a new <tt>UnexpectedFileIOException<tt>. * * @param lockFile the underlying <tt>LockFile</tt> object * @param inMethod the name of the method in which the exception * was originally thrown (may be passed up several levels) * @param reason the underlying exception */
public UnexpectedFileIOException(final LockFile lockFile, final String inMethod, final IOException reason) { super(lockFile, inMethod); this.reason = reason; }
Retrieves the underlying IOException.
Returns:Value of property reason.
/** * Retrieves the underlying <tt>IOException</tt>. * * @return Value of property reason. */
public IOException getReason() { return this.reason; }
Subclass-specific override.
Returns:representation of lockFile, inMethod and reason, as a String object
/** * Subclass-specific override. * * @return representation of <tt>lockFile</tt>, <tt>inMethod</tt> and * <tt>reason</tt>, as a <tt>String</tt> object */
public String getMessage() { // override return super.getMessage() + " reason: " + reason; } }
Thrown when access to a LockFile object's target file raises an unexpected FileNotFoundException.
/** * Thrown when access to a <tt>LockFile</tt> object's target file raises an * unexpected <tt>FileNotFoundException</tt>. */
public static final class UnexpectedFileNotFoundException extends BaseException { private final FileNotFoundException reason;
Constructs a new UnexpectedFileNotFoundException.

Params:
  • lockFile – the underlying LockFile object
  • inMethod – the name of the method in which the exception was originally thrown (may be passed up several levels)
  • reason – the underlying exception
/** * Constructs a new <tt>UnexpectedFileNotFoundException<tt>. <p> * * @param lockFile the underlying <tt>LockFile</tt> object * @param inMethod the name of the method in which the exception * was originally thrown (may be passed up several levels) * @param reason the underlying exception */
public UnexpectedFileNotFoundException( final LockFile lockFile, final String inMethod, final FileNotFoundException reason) { super(lockFile, inMethod); this.reason = reason; }
Retrieves the underlying FileNotFoundException.
Returns:Value of property reason.
/** * Retrieves the underlying FileNotFoundException. * * @return Value of property reason. */
public FileNotFoundException getReason() { return this.reason; }
Subclass-specific override.
Returns:representation of lockFile, inMethod and reason, as a String object
/** * Subclass-specific override. * * @return representation of lockFile, inMethod and reason, as * a String object */
public String getMessage() { // override return super.getMessage() + " reason: " + reason; } }
Thrown when it is detected that a LockFile object's target file does not have the expected length.
/** * Thrown when it is detected that a LockFile object's target file does not * have the expected length. */
public static final class WrongLengthException extends BaseException { private final long length;
Constructs a new WrongLengthException.
Params:
  • lockFile – the underlying LockFile object
  • inMethod – the name of the method in which the exception was originally thrown (may be passed up several levels)
  • length – the actual length reported by the file system
/** * Constructs a new WrongLengthException. * * @param lockFile the underlying LockFile object * @param inMethod the name of the method in which the exception * was originally thrown (may be passed up several levels) * @param length the actual length reported by the file system */
public WrongLengthException(final LockFile lockFile, final String inMethod, final long length) { super(lockFile, inMethod); this.length = length; }
Retrieves the actual length reported by the file system.
Returns:the actual length reported by the file system
/** * Retrieves the actual length reported by the file system. * * @return the actual length reported by the file system */
public long getLength() { return this.length; }
Subclass-specific override.
Returns:representation of lockFile, inMethod and length, as a String object
/** * Subclass-specific override. * * @return representation of lockFile, inMethod and length, as * a String object */
public String getMessage() { // override return super.getMessage() + " length: " + length; } }
Thrown when it is detected that a LockFile object's target file does not start with the expected MAGIC value.
/** * Thrown when it is detected that a LockFile object's target file does not * start with the expected MAGIC value. */
public static final class WrongMagicException extends BaseException { private final byte[] magic;
Constructs a new WrongMagicException.
Params:
  • lockFile – the underlying LockFile object
  • inMethod – the name of the method in which the exception was originally thrown (may be passed up several levels)
  • magic – the actual magic value read from the file
/** * Constructs a new WrongMagicException. * * @param lockFile the underlying LockFile object * @param inMethod the name of the method in which the exception * was originally thrown (may be passed up several levels) * @param magic the actual magic value read from the file */
public WrongMagicException(final LockFile lockFile, final String inMethod, final byte[] magic) { super(lockFile, inMethod); this.magic = magic; }
Subclass-specific override.
Returns:representation of inMethod, file and magic, as a String object
/** * Subclass-specific override. * * @return representation of inMethod, file and magic, * as a String object */
public String getMessage() { // override String message = super.getMessage() + " magic: "; message = message + ((magic == null) ? "null" : "'" + StringConverter.byteArrayToHexString(magic) + "'"); return message; }
Retrieves a copy of the actual MAGIC value read from the file.

Returns:a copy of the actual MAGIC value read from the file
/** * Retrieves a copy of the actual <tt>MAGIC</tt> value read from the * file. <p> * * @return a copy of the actual <tt>MAGIC</tt> value read from the file */
public byte[] getMagic() { return (magic == null) ? null : this.magic.clone(); } } }