alvinalexander.com | career | drupal | java | mac | mysql | perl | scala | uml | unix  

HSQLDB example source code file (LockFile.java)

This example HSQLDB source code file (LockFile.java) is included in the DevDaily.com "Java Source Code Warehouse" project. The intent of this project is to help you "Learn Java by Example" TM.

Java - HSQLDB tags/keywords

baseexception, eofexception, exception, filesecurityexception, filesecurityexception, io, ioexception, ioexception, lockfile, lockfile, securityexception, string, string, unexpectedendoffileexception, unexpectedfileioexception

The HSQLDB LockFile.java source code

/* Copyright (c) 2001-2008, 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.Trace;
import org.hsqldb.lib.FileUtil;
import org.hsqldb.lib.HsqlTimer;
import org.hsqldb.lib.StringConverter;

/**
 * Base cooperative file locking implementation and <tt>LockFile
 * 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>* 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">*
 * 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 aquire 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:

* * <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 exeption 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 * exeption 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>tm), then this is likely a non-issue. And if * this case makes possible a race condition with another * <tt>LockFile 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 unlikely that * so unfortunate a timing will occur as to allow simultaneous * lock conditions to be established. Finally, if some * non-<tt>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.<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 MAGIC and heartbeat * timestamp values, fast failing the current poll if the stream * cannot be opened.<p> * * <li>Test the file's MAGIC 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 MAGIC 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. * * <li>A kernel-enforced manditory or advisory file lock was held. * </ul>

* * 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:

* <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 have failed.

* </ol> * </ol>

* * <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() * produces {@link org.hsqldb.persist.NIOLockFile NIOLockFile} instances.<p> * * When <tt>NIOLockFile instances are produced, then it is possible that * true kernel-enforced advisory or manditory file locking is used to protect * the underlying lock file from inadvertent modification (and possibly even * from deletion, including deletion by the system superuser). * * Otherwise, <tt>newLockFile() produces vanilla LockFile * instances, which exhibit just the elementary cooperative locking behavior on * platforms that do not, by default, implement kernel-enforced manditory * locking for open files. <p> * * At this point, it must be noted that not every target platform upon which * Java can run actually provides true kernel-enforced manditory (or even * advisory) file locking. Indeed, even when a target platform <em>does * provide such locking guarantees for local file systems, it may not be able * to do so for network file systems, or it may only be able to do so safely * (or at all) with certain restrictions. Further, external system configuration * may be a prerequisite to enable manditory locking on systems that support it * but employ advisory locking by default. <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 * distiguish between simple lack of platform support and cases involving * immature Java runtimes that do not fully or correctly implement all NIO * features (and hence may throw exceptions at unexpected times or in places * where the API specification indicates none can be thrown).<p> * * It is for the preceding reasons that, as of HSQLDB 1.8.0.3, * <tt>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 * <tt>FileLock and failed, then the enclosing cooperative lock attempt * failed also, despite the fact that a vanilla locking approach could * succeed. <p> * * <b>Polling Configuration:

* * 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:

* * 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 * {@link 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<property-name>=true directive). Be aware that * the system property value is read only once, in the static initializer block * for this class. <p> * * <b>Design Notes:

* * 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 comprimse under varying * and generally uncontrollable JVM, OS and hardware platform * limitations/capabilites, as well as under usability considerations and * external security or operating constraints that may need to be imposed.<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 descendent exists specifically * because it was determined though experimenatation that * <tt>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 * <tt>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 <tt>LockFile * 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 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() * method was reflectively invoked. <p> * * To solve this, the original <tt>LockFile class was split in two and * instead of reflective method invocation, subclass instantiation is now * performed at the level of the <tt>newLockFile() factory method. * Similarly, the HSQLDB ANT build script now detects the presence or abscence * of JDK 1.4+ features such as java.nio and only attempts to build and deploy * <tt>NIOLockFile to the hsqldb.jar if such features are reported * present. <p> * * @author Campbell Boucher-Burnett (boucherb@users dot sourceforge.net) * @version 1.8.1 * @since 1.7.2 */ public class LockFile { /** * Arbitary 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; /** * {@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. <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:

* * "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. <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 {@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. */ 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. */ public static final String POLL_INTERVAL_PROPERTY = "hsqldb.lockfile.poll.interval"; /** Whether <tt>java.nio 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. */ public static final String USE_NIO_FILELOCK_PROPERTY = "hsqldb.lockfile.nio.filelock"; /** * Statically computed indication of <tt>java.nio.channels.FileLock * runtime availability. <p> * * <b>Design Note:

* * Computed in a static initializer block. Will be <tt>false if * <tt>USE_NIO_FILELOCK_PROPERTY is false at static * initialization, regardless of actual availability. */ public static final boolean NIO_FILELOCK_AVAILABLE; /** * Statically computed reference to the <tt>NIOLockFile class.

* * <b>Design Note:

* * Computed in a static initializer block. Will be <tt>null if * <tt>USE_NIO_FILELOCK_PROPERTY is false 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. */ 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 referecing a subclass whose static // field initializtion 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. <p> * * <b>Design Note:

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

* * Should really be final, but finality makes reflective construction * and adherence to desirable <tt>LockFile factory method event * sequence much more complicated. */ private String cpath; /** * A <tt>RandomAccessFile constructed from this object's canonical file * reference. <p> * * This <tt>RandomAccessFile 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. */ protected volatile boolean locked; /** Opaque reference to this object's heatbeat task. */ private volatile Object timerTask; /** * Retrieves a new <tt>NIOLockFile, or null if not available * under the current runtime environment. * * @return a new <tt>NIOLockFile, or null if not available * under the current runtime environment */ private static final LockFile newNIOLockFile() { if (NIO_FILELOCK_AVAILABLE && NIO_LOCKFILE_CLASS != null) { try { return (LockFile) NIO_LOCKFILE_CLASS.newInstance(); } catch (Exception e) { // e.printStackTrace() } } return null; } /** * To allow subclassing without exposing a public constructor. */ protected LockFile() {} /** * Retrieves a <tt>LockFile instance, initialized with a File * object whose path is the canonical form of the one specified by the * given <tt>path argument.

* * The resulting <tt>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. * * @return a <tt>LockFile instance initialized with a File * object whose path is the one specified by the given <tt>path * argument. * @param path the path of the <tt>File object with which the retrieved * <tt>LockFile 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 final LockFile newLockFile(final String path) throws FileCanonicalizationException, FileSecurityException { LockFile lockFile = newNIOLockFile(); if (lockFile == null) { lockFile = new LockFile(); } lockFile.setPath(path); return lockFile; } /** * {@link org.hsqldb.persist.Logger#acquireLock(java.lang.String)} * delegate.<p> * * Retrieves a new <tt>LockFile 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 object holding a cooperative lock * condition upon the file with the given path, appended with the * extension '.lck' */ public static final LockFile newLockFileLock(final String path) throws HsqlException { LockFile lockFile = null; try { lockFile = LockFile.newLockFile(path + ".lck"); } catch (LockFile.BaseException e) { throw Trace.error(Trace.LOCK_FILE_ACQUISITION_FAILURE, e.getMessage()); } boolean locked = false; try { locked = lockFile.tryLock(); } catch (LockFile.BaseException e) { throw Trace.error(Trace.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 Trace.error(Trace.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 <tt>LockFile instance) or stale.

* * The check conforms to the following rules: <p> * * <ol> * <li>If the parameter withCreateNewFile is true, {@link * java.io.File#createNewFile()} is available and its invocation * upon this object's <tt>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 <em>race 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(); 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>Windowstm * </sup>), then this is likely a non-issue. And if this case makes * possible a race condition with another <tt>LockFile object * (because the test for existence yeilds false and subsequent file * creation is not atomic relative to all other file system actions), it * is still <em>very unlikely that so unfortunate a timing will * occur as to allow simultaneous lock conditions to be established. * Finally, if some non-<tt>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.<p> * * <li>If a Java security exception is thrown while testing for existence, * it is rethrown as a <tt>FileSecurityException. * * <li>Read the file's length. * * <li>If a Java security exception is thrown reading length, it is rethrown * as a <tt>FileSecurityException (it is possible somebody * concurrently refreshed the system Policy in the interim). * * <li>If the file does not have the expected length, a * <tt>WrongLengthException 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 MAGIC and heartbeat * timestamp values. * * <li>If a file not found exception is thrown above, it is rethrown as an * <tt>UnexpectedFileNotFoundException (we've already tested for * existence). * * <li>If a Java security exception is thrown above, it is rethrown as a * <tt>FileSecurityException (it is possible somebody * concurrently refreshed the system Policy in the interim). * * <li>Read the MAGIC value. * * <li>If an end of file exepction is thrown above, it is rethrown as an * <tt>UnexpectedEndOfFileException (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 (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 MAGIC value, * a <tt>WrongMagicException 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 (it is possible somebody * concurrently refreshed the system Policy in the interim). * * <li>If an end of file exection is thrown above, it is rethrown as an * <tt>UnexpectedEndOfFileException (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 (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 is thrown. * * <li>Otherwise, this method simply returns. * </ol> * * @param withCreateNewFile if <tt>true, attempt to employ * <tt>File.createNewFile() as part of the check so as to * eliminate potential race conditions when establising 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 into the past (or future) * @throws UnexpectedEndOfFileException if an <tt>EOFExceoption is * thrown while reading either the magic or heartbeat timestamp values * @throws UnexpectedFileIOException if an <tt>IOException other than * <tt>EOFException is thrown while reading either the magic or * heartbeat timestamp values * @throws UnexpectedFileNotFoundException if a * <tt>FileNotFoundException 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; //#ifdef JAVA2FULL 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); } //#else /* if (!file.exists()) { if (withCreateNewFile) { openRAF(); closeRAF(); } return; } length = file.length(); */ //#endif JAVA2 if (length != USED_REGION) { 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 {@link #raf RandomAccessFile}. <p> * * As a side-effect, the associated <tt>FileChannel object, if any, * is closed as well. * * @throws UnexpectedFileIOException if an <tt>IOException 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 {@link #tryLock() * tryLock()} template method. <p> * * Descendents are free to provide additional functionality here, * using the following rules: <p> * * <b>PRE:

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

* * From this, it can be inferred that upon entry: <p> * * <ol> * <li>locked == false. * <li>raf is a non-null instance that can be used to get a * <tt>FileChannel instance, if desired. * <li>the underlying file either did not exist before invoking * <tt>openRAF() 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 USED_REGION, * <li>had the expected length and MAGIC value and * <li>had a stale heartbeat timestamp value. * </ol> * </ol>

* * 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 instance or as the result of a previous, * successful <tt>tryLock() invocation upon this LockFile * 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:

* * This method should return <tt>false if optional locking work is not * performed or if it fails, else <tt>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 * <tt>false return value to avoid calling {@link * #doOptionalReleaseActions() doOptionalReleaseActions()} as part of the * {@link #tryRelease() tryRelease()} method. <p> * * <b>Note:

* * The default implementation does nothing and always returns * <tt>false.

* * @return <tt>true if optional lock actions are performed and they * succeed, else <tt>false */ protected boolean doOptionalLockActions() { return false; } /** * Provides any optional release actions for the {@link #tryRelease() * tryRelease()} template method. <p> * * <b>PRE:

* * It is important that this method does not rethrow any exceptions * it encounters as unchecked exceptions to the calling context. <p> * * <b>POST:

* * In general, <tt>false should be returned if optional locking work * is not performed or if it fails, else <tt>true. However, the return * value is currenly treated as purely informative. <p> * * <b>Note:

* * The default implementation does nothing and always returns false. <p> * * @return <tt>true if optional release actions are performed and they * succeed, else <tt>false */ protected boolean doOptionalReleaseActions() { return false; } /** * Initializes this object with a <tt>File object whose path has the * canonical form of the given <tt>path argument.

* * <b>PRE:

* * <ol> * <li>This method is called once and only once per * <tt>Lockfile instance. * * <li>It is always the first method called after * <tt>LockFile construction * * <li>The supplied path argument is never * <tt>null. * </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} 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.getDefaultInstance().canonicalOrAbsolutePath(path); this.file = new File(path); try { FileUtil.getDefaultInstance().makeParentDirectories(this.file); } catch (SecurityException ex) { throw new FileSecurityException(this, "setPath", ex); } try { this.file = FileUtil.getDefaultInstance().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 {@link #raf RandomAccessFile}. <p> * * @throws UnexpectedFileNotFoundException if a * <tt>FileNotFoundException is thrown in reponse to * constructing the <tt>RandomAccessFile 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} 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); } catch (IOException ex) { throw new UnexpectedFileIOException(this, "openRAF", ex); } } /** * Checks whether the given <tt>DataInputStream 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} method * denies read access to the file * @throws UnexpectedEndOfFileException if an <tt>EOFException is * thrown while reading the <tt>DataInputStream * @throws UnexpectedFileIOException if an <tt>IOException other than * <tt>EOFException is thrown while reading the * <tt>DataInputStream * @throws WrongMagicException if a value other than <tt>MAGIC is read * from the <tt>DataInputStream */ 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 <tt>Long.MIN_VALUE * </tt> (the earliest time representable as a long in Java) is * returned immediately. <p> * * @return the hearbeat timestamp read from this object's lock file, * as a <tt>long value or, if this object's lock * file does not exist, <tt>Long.MIN_VALUE, the earliest time * representable as a <tt>long 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} method * denies read access to the file * @throws UnexpectedEndOfFileException if an <tt>EOFException is * thrown while attempting to read the target file's <tt>MAGIC * 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 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. */ 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. */ private final void stopHeartbeat() { if (timerTask != null && !HsqlTimer.isCancelled(timerTask)) { HsqlTimer.cancel(timerTask); timerTask = null; } } /** * Writes the {@link #MAGIC} value to this object's lock file that * distiguishes 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). However, it is conceivable that these * native methods may, in turn, access Java methods that do * throw <tt>SecurityException. In this case, a * <tt>SecurityException 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 value to the * target file (typically, this cannot happen, but the case is * included to distiguish it from the general <tt>IOException * case). * @throws UnexpectedFileIOException if any other I/O error occurs while * attepting to write the <tt>MAGIC 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. <p> * * @throws FileSecurityException possibly never (seek and write are native * methods whose JavaDoc entries do not actually specifiy throwing * <tt>SecurityException). However, it is conceivable that these * native methods may, in turn, access Java methods that do throw * <tt>SecurityException. In this case, a * <tt>SecurityException 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 attepting to write the heartbeat timestamp value to * the target file (typically, this cannot happen, but the case is * included to distiguish 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. <p> * * An object is considered equal to a <tt>LockFile object if and * only if it is not null, it is an instance of <tt>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 <tt>LockFile, and the expression:

* * <pre> * this == other || * this.file == null ? other.file == null : this.file.equals(other.file); * </pre> * * yeilds true. <p> * * Note that <tt>file must be a canonical reference to correctly * satisfy this contract. <p> * * @param obj the reference object with which to compare. * @return <tt>true if this object is equal to the obj * argument; <tt>false 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 * <tt>String object.

* * @return the canonical path of this object's lock file. */ public final String getCanonicalPath() { return cpath; } /** * Retrieves the hash code value for this object. <p> * * The value is zero if <tt>file is null, else the * <tt>hashCode of file. That is, two LockFile * objects have the same <tt>hashCode value if they refer to the * same lock file. <p> * * Note that <tt>file 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. <p> * * <b>Note:

* * Due to platform-independence retrictions placed on a JVM, it is quite * possible to successfully acquire a lock condition and yet for the * condition to become invalid while still held. <p> * * For instance, under JVMs with no <tt>java.nio package or under * operating systems that do not apply mandatory file locking (espcially * mandatory locking that precludes deletion), it is quite possible for * another process or even an uncooperative bit of code running in the same * JVM to overwrite or delete the target lock file while this object holds * a lock condition. <p> * * Because of this, the <tt>isValid() 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 if this object has successfully obtained and is * still holding (has not yet released) a lock condition, else * <tt>false * @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 aquired using the specified path. * * @param path the path to test * @return <tt>true if there is currently something preventing the * acquisition of a cooperative lock condition using the specified * <tt>path, else false */ public final 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. <p> * * More formally, this method retrieves true if and only if: <p> * * <pre> * isLocked() && file != null && file.exists() && raf != null * </pre> * * @return <tt>true if this object holds a valid lock condition on its * lock file; else <tt>false * @throws SecurityException if a required system property value cannot * be accessed, or if a Java security manager exists and its * <tt>checkRead 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. <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 StringBuffer(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 * <tt>toString() method.

* * The default implementation returns the empty string. * * @return an implementation-specific tail value for the <tt>toString() * method * @see #toString */ protected String toStringImpl() { return ""; } /** * Retrieves the number of times <tt>checkHeartbeat may fail before * <tt>pollHeartbeat fails as a consequence.

* * The value is obtained in the following manner: <p> * * <ol> * <li>retries is assigned POLL_RETRIES_DEFAULT. * * <li>retries is assigned 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 may fail before * <tt>pollHeartbeat 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 <tt>pollHeartbeat * waits between failed invocations of <tt>checkHeartbeat. * * The value is obtained in the following manner: <p> * * <ol> * <li>interval is assigned 10 + (HEARTBEAT_INTERVAL_PADDED * getPollHeartbeatRetries())</tt> * * <li>interval is assigned 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()) * </ol> * * @return the interval, in milliseconds, that <tt>pollHeartbeat * waits between failed invocations of <tt>checkHeartbeat */ 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. <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 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. * @throws UnexpectedFileNotFoundException if the target file became * unavailable between a test for existence and an attempt to read * the <tt>MAGIC or heartbeat timestamp value. * @throws UnexpectedEndOfFileException if an <tt>EOFException was * raised while trying to read the <tt>MAGIC or heartbeat * timestamp value of the target file * @throws UnexpectedFileIOException if an <tt>EOFException other than * <tt>EOFException was raised while trying to read the * <tt>MAGIC 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 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. <p> * * @return <tt>true if this object already holds a lock or the lock was * obtained successfully, else <tt>false * @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. * @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 or heartbeat timestamp value. * @throws UnexpectedEndOfFileException if the lock condition could not be * obtained because <tt>EOFException was raised while trying to * read the <tt>MAGIC or heartbeat timestamp value of the target * file * @throws UnexpectedFileIOException if the lock condition could not be * obtained due to an <tt>IOException other than * <tt>EOFException * @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 value * @return <tt>true if and only if a lock condition is obtained; * <tt>false otherwise. In general, an exception will * <em>always 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 // sucession: 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 occured, 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.getDefaultInstance().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 perfomed 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. <p> * * * @return <tt>true if this object does not currently hold a * lock condition or the lock is released completely (including * successful file deletion), else <tt>false. * @throws FileSecurityException if a <tt>SecurityException 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 extenally 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; } /** * Attempts to release this object's cooperative lock condition. <p> * * @throws Throwable if this object encounters an unhandled exception * while trying to release the cooperative lock condition */ protected final void finalize() throws Throwable { this.tryRelease(); } /** * 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) { Trace.printSystemOut(t.toString()); } } } /** * 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 <tt>LockFile.BaseException.

* * @param lockFile the underlying <tt>LockFile object * @param inMethod the name of the method in which the exception * was originally thrown (may be passed up serveral 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. <p> * * @return representation of <tt>lockFile and * <tt>inMethod, as String object */ public String getMessage() { // override return "lockFile: " + lockFile + " method: " + inMethod; } /** * Getter for <tt>inMethod property.

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

* * @return the underlying <tt>LockFile object */ public LockFile getLockFile() { return this.lockFile; } } /** * Thrown when canonicalization of a <tt>LockFile 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 <tt>FileCanonicalizationException.

* * @param lockFile the underlying <tt>LockFile object * @param inMethod the name of the method in which the exception * was originally thrown (may be passed up serveral 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 <tt>IOException.

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

* * 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 <tt>FileSecurityException.

* * @param lockFile the underlying LockFile object * @param inMethod the name of the method in which the exception * was originally thrown (may be passed up serveral 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 <tt>SecurityException.

* * @return Value of property reason. */ public SecurityException getReason() { return this.reason; } /** * 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 * aquisition. <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 <tt>LockHeldExternallyException.

* * @param lockFile the underlying <tt>LockFile object * @param inMethod the name of the method in which the exception * was originally thrown (may be passed up serveral 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 <tt>heartbeat attribute.

* * @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 <tt>read attribute.

* * @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. <p> * * @return representation of <tt>lockFile, inMethod, * <tt>read and heartbeat, as a String * 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 <tt>LockFile object's target file raises an * unexpected <tt>EOFException. */ public static final class UnexpectedEndOfFileException extends BaseException { private final EOFException reason; /** * Constructs a new <tt>UnexpectedEndOfFileException.

* * @param lockFile the underlying <tt>LockFile object * @param inMethod the name of the method in which the exception * was originally thrown (may be passed up serveral 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 <tt>EOFException.

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

* * @param lockFile the underlying <tt>LockFile object * @param inMethod the name of the method in which the exception * was originally thrown (may be passed up serveral 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. * * @return Value of property reason. */ public FileNotFoundException getReason() { return this.reason; } /** * 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. */ public static final class WrongLengthException extends BaseException { private final long length; /** * 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 serveral 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; } /** * Retreives 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. * * @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. */ public static final class WrongMagicException extends BaseException { private final byte[] magic; /** * 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 serveral 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. * * @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.byteToHex(magic) + "'"); return message; } /** * Retrieves a copy of the actual <tt>MAGIC value read from the * file. <p> * * @return a copy of the actual <tt>MAGIC value read from the file */ public byte[] getMagic() { return (magic == null) ? null : (byte[]) this.magic.clone(); } } }

Other HSQLDB examples (source code examples)

Here is a short list of links related to this HSQLDB LockFile.java source code file:

... this post is sponsored by my books ...

#1 New Release!

FP Best Seller

 

new blog posts

 

Copyright 1998-2024 Alvin Alexander, alvinalexander.com
All Rights Reserved.

A percentage of advertising revenue from
pages under the /java/jwarehouse URI on this website is
paid back to open source projects.