001: /* Copyright (c) 2001-2005, The HSQL Development Group
002: * All rights reserved.
003: *
004: * Redistribution and use in source and binary forms, with or without
005: * modification, are permitted provided that the following conditions are met:
006: *
007: * Redistributions of source code must retain the above copyright notice, this
008: * list of conditions and the following disclaimer.
009: *
010: * Redistributions in binary form must reproduce the above copyright notice,
011: * this list of conditions and the following disclaimer in the documentation
012: * and/or other materials provided with the distribution.
013: *
014: * Neither the name of the HSQL Development Group nor the names of its
015: * contributors may be used to endorse or promote products derived from this
016: * software without specific prior written permission.
017: *
018: * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
019: * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
020: * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
021: * ARE DISCLAIMED. IN NO EVENT SHALL HSQL DEVELOPMENT GROUP, HSQLDB.ORG,
022: * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
023: * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
024: * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
025: * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
026: * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
027: * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
028: * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
029: */
030:
031: package org.hsqldb.persist;
032:
033: import java.io.DataInputStream;
034: import java.io.File;
035: import java.io.FileInputStream;
036: import java.io.RandomAccessFile;
037:
038: import org.hsqldb.DatabaseManager;
039: import org.hsqldb.HsqlDateTime;
040: import org.hsqldb.HsqlException;
041: import org.hsqldb.Trace;
042: import org.hsqldb.lib.FileUtil;
043: import org.hsqldb.lib.HsqlTimer;
044: import org.hsqldb.lib.java.JavaSystem;
045:
046: /**
047: * The base HSQLDB cooperative file locking implementation and factory. <p>
048: *
049: * <hr>
050: *
051: * Here is the way this class operates: <p>
052: *
053: * <ol>
054: * <li>A file with a well-known path relative to each database instance
055: * is used to implement cooperative locking of database files across
056: * process boundaries (database instances running in different JVM
057: * host processes) and class loader contexts (databases whose classes
058: * have been loaded by distinct class loaders such that their open
059: * database repositories are distinct and are inaccessible across
060: * the class loader context boundaries).<p>
061: *
062: * <li>A background thread periodically writes a timestamp to this object's
063: * lock file at {@link #HEARTBEAT_INTERVAL} millisecond intervals,
064: * acting as a heartbeat to indicate that a lock is still held.<p>
065: *
066: * <li>The generic lock attempt rules are: <p>
067: * <ul>
068: * <li>If a lock condition is already held by this object, do nothing and
069: * signify that the lock attempt was successful, else...<p>
070: *
071: * <li>If no lock file exists, go ahead and create one, silently issue the
072: * {@link java.io.File#deleteOnExit File.deleteOnExit()} directive via
073: * refelective method invocation (in order to stay JDK 1.1 compliant),
074: * schedule a periodic heartbeat task and signify that the lock attempt
075: * was successful, else...<p>
076: *
077: * <li>The lock file must already exist, so try to read its heartbeat
078: * timestamp. If the read fails, assume that a lock condition is held by
079: * another process or a database in an inaccessible class loader context
080: * and signify that the attempt failed, else if the read value
081: * is less than <code>HEARTBEAT_INTERVAL</code> milliseconds into the
082: * past or futuer, assume that a lock condition is held by another
083: * process or a database in an inaccessible class loader context and
084: * signify that the lock attempt failed, else assume that the file is
085: * not in use, schedule a periodic heartbeat task and signify that the
086: * lock attempt was successful.<p>
087: *
088: * </ul>
089: * <li>The generic release attempt rules are:<p>
090: * <ul>
091: * <li>If a lock condition is not currently held, do nothing and signify
092: * that the release attempt was successful, else...<p>
093: *
094: * <li>A lock condition is currently held, so try to release it. By
095: * default, releasing the lock condition consists of closing and
096: * nullifying any objects that have a file descriptor open on the
097: * lock file. If the release is successful, cancel the periodic
098: * heartbeat task and signify that the release succeeded, else signify
099: * that the release attempt failed.<p>
100: * </ul>
101: * </ol> <p>
102: *
103: * In addition to the generic lock and release rules, the protected methods
104: * {@link #lockImpl() lockImpl()} and {@link #releaseImpl() releaseImpl()}
105: * are called during lock and release attempts, respectively. This allows
106: * transparent, JDK 1.1 compliant integration of extended strategies for
107: * locking and releasing, based on subclassing and reflective construction
108: * of such specializations in the factory method
109: * {@link #newLockFile newLockFile()}, determined by information gathered
110: * at run-time. <p>
111: *
112: * In particular, if it is available at runtime, then newLockFile() retrieves
113: * instances of {@link org.hsqldb.NIOLockFile NIOLockFile} to capitalize,
114: * when possible, on the existence of the {@link java.nio.channels.FileLock
115: * FileLock} class. If the <code>NIOLockFile</code> class does not exist at
116: * run-time or the java.nio classes it uses are not supported under the
117: * run-time JVM, then newLockFile() produces vanilla LockFile instances,
118: * meaning that only purely cooperative locking takes place, as opposed to
119: * possibly O/S-enforced file locking which, at least in theory, is made
120: * available through the {@link java.nio.channels} package). However, it
121: * must be noted that even if a JVM implementation provides the full
122: * java.nio.channels package, it is not absolutely required to guarantee
123: * that the underlying platform (the current operating system) provides
124: * true process-wide file locking. <p>
125: *
126: * <b>Note:</b> <p>
127: *
128: * The <code>NIOLockFile</code> descendent exists because it has been determined
129: * though experimenatation that <code>java.nio.channels.FileLock</code>
130: * does not always exhibit the correct/desired behaviour under reflective
131: * method invocation. That is, it has been discovered that under some operating
132: * system/JVM combinations, after calling <code>FileLock.release()</code>
133: * via a reflective method invocation, the lock is not released properly,
134: * deletion of the lock file is not possible even from the owning object
135: * (this) and it is impossible for other <code>LockFile</code> instances
136: * or any other objects or processes to successfully obtain a lock
137: * condition on the lock file, despite the fact that the <code>FileLock</code>
138: * object reports that its lock is invalid (was released successfully).
139: * Frustratingly, this condition appears to persist until full exit of the
140: * JVM process in which the <code>FileLock.tryLock()</code> method was
141: * reflectively invoked. <p>
142: *
143: * To solve this, the original <code>LockFile</code> class was split in two and
144: * instead of reflective method invocation, reflection-based class
145: * instantiation is now performed at the level of the <code>newLockFile()</code>
146: * factory method. Similarly, the HSQLDB ANT build script detects the presence
147: * or abscence of JDK 1.4 features such as java.nio and only attempts to build
148: * and deploy <code>NIOLockFile</code> to the hsqldb.jar if such features are
149: * reported present. </p>
150: *
151: * @author boucherb@users
152: * @version 1.7.2
153: * @since 1.7.2
154: */
155: public class LockFile {
156:
157: /** Canonical reference to this object's lock file. */
158: protected File f;
159:
160: /** Cached value of the lock file's canonical path. */
161: private String cpath = null;
162:
163: /**
164: * A RandomAccessFile constructed from this object's reference, f, to its
165: * lock file. <p>
166: *
167: * This RandomAccessFile is used to periodically write out the heartbeat
168: * timestamp to this object's lock file.
169: */
170: protected RandomAccessFile raf;
171:
172: /**
173: * The period, in milliseconds, at which heartbeat timestamps are written
174: * to this object's lock file.
175: */
176: public static final long HEARTBEAT_INTERVAL = 10000;
177:
178: /**
179: * A magic value to place at the beginning of the lock file to
180: * differentiate it from other files. The value is "HSQLLOCK".getBytes().
181: */
182: public static final byte[] MAGIC = "HSQLLOCK".getBytes();
183:
184: /** Indicates whether this object has a lock condition on its lock file. */
185: protected boolean locked;
186:
187: /**
188: * The timed scheduler with which to register this object's
189: * heartbeat task.
190: */
191: protected static final HsqlTimer timer = DatabaseManager.getTimer();
192:
193: /**
194: * An opaque reference to this object's heatbeat task.
195: */
196: private Object timerTask;
197:
198: /**
199: * Attempts to read the hearbeat timestamp from this object's lock file
200: * and compare it with the current time. <p>
201: *
202: * An exception is thrown if it must be presumned that another process has
203: * locked the file, using the following rules: <p>
204: *
205: * <ol>
206: * <li>If the file does not exist, this method returns immediately.
207: *
208: * <li>If an exception is raised reading the file, then an exeption is
209: * thrown.
210: *
211: * <li>If the read is successful and the timestamp read in is less than
212: * <code>HEARTBEAT_INTERVAL</code> milliseconds into the past or
213: * future, then an exception is thrown.
214: *
215: * <li>If no exception is thrown in 2.) or 3.), this method simply returns.
216: * </ol>
217: * @throws Exception if it must be presumed that another process
218: * or isolated class loader context currently has a
219: * lock condition on this object's lock file
220: */
221: private void checkHeartbeat() throws Exception {
222:
223: long lastHeartbeat;
224: String mn;
225: String path;
226:
227: mn = "checkHeartbeat(): ";
228: path = "lock file [" + cpath + "]";
229:
230: trace(mn + "entered.");
231:
232: if (!f.exists()) {
233: trace(mn + path + " does not exist. Check OK.");
234:
235: return;
236: }
237:
238: if (f.length() != 16) {
239: trace(mn + path + " length != 16; Check OK.");
240:
241: return;
242: }
243:
244: try {
245: lastHeartbeat = System.currentTimeMillis()
246: - readHeartbeat();
247: } catch (Exception e) {
248:
249: // e.printStackTrace();
250: throw new Exception(Trace.getMessage(
251: Trace.LockFile_checkHeartbeat, true, new Object[] {
252: e.toString(), cpath }));
253: }
254:
255: trace(mn + path + " last heartbeat " + lastHeartbeat
256: + " ms ago.");
257:
258: if (Math.abs(lastHeartbeat) < HEARTBEAT_INTERVAL) {
259: throw new Exception(Trace.getMessage(
260: Trace.LockFile_checkHeartbeat2, true, new Object[] {
261: mn, path }));
262: }
263: }
264:
265: /**
266: * Closes this object's {@link #raf RandomAccessFile}.
267: *
268: * @throws Exception if an IOException occurs
269: */
270: private void closeRAF() throws Exception {
271:
272: String mn;
273:
274: mn = "closeRAF(): ";
275:
276: trace(mn + "entered.");
277:
278: if (raf == null) {
279: trace(mn + "raf was null upon entry. Exiting immediately.");
280: } else {
281: trace(mn + "closing " + raf);
282: raf.close();
283: trace(mn + raf + " closed successfully. Setting raf null");
284:
285: raf = null;
286: }
287: }
288:
289: /**
290: * Initializes this object with the specified <code>File</code>
291: * object. <p>
292: *
293: * The file argument is a reference to this object's lock file. <p>
294: *
295: * This action has the effect of attempting to release any existing
296: * lock condition and reinitializing all lock-related member attributes
297: * @param file a reference to the file this object is to use as its
298: * lock file
299: */
300: private void setFile(File file) throws Exception {
301:
302: if (isLocked()) {
303: try {
304: tryRelease();
305: } catch (Exception e) {
306: trace(e);
307: }
308: }
309:
310: f = FileUtil.canonicalFile(file);
311: cpath = f.getPath();
312: raf = null;
313: locked = false;
314: }
315:
316: /**
317: * Provides any specialized locking actions for the
318: * {@link #tryLock() tryLock()} method. <p>
319: *
320: * Descendents are free to provide additional functionality here,
321: * using the following rules:
322: *
323: * <pre>
324: * PRE:
325: *
326: * This method is only called if tryLock() thinks it needs to get a lock
327: * condition, so it can be assumed the locked == false upon entry, raf is
328: * a non-null instance that can be used to get a FileChannel if desired,
329: * and the lock file is, at the very least, readable. Further, this
330: * object's heatbeat task is definitely cancelled and/or has not yet been
331: * scheduled, so whatever timestamp is recorded in the lock file, if it
332: * exists, is what was written by a previous locker, if any. A timestamp
333: * value in a preexisting file is only considered valid if the file is
334: * of the correct length and its first eight bytes are
335: * the value {@link #MAGIC MAGIC}.
336: *
337: * POST:
338: *
339: * This method must return false if any additional locking work fails,
340: * else true.
341: * </pre>
342: *
343: * The default implementation of this method reflectively (for JDK1.1
344: * compliance) invokes f.deleteOnExit() in a silent manner and always
345: * returns true. <p>
346: *
347: * @throws Exception if a situation is encountered that absolutely
348: * prevents the status of the lock condtion
349: * to be determined. (e.g. an IO exception
350: * occurs here)
351: * @return <code>true</code> if no extended locking
352: * actions are taken or the actions succeed,
353: * else <code>false</code>.
354: */
355: protected boolean lockImpl() throws Exception {
356:
357: String mn;
358:
359: mn = "lockImpl(): ";
360:
361: trace(mn + "entered.");
362: FileUtil.deleteOnExit(f);
363:
364: return true;
365: }
366:
367: /**
368: * Opens this object's {@link #raf RandomAccessFile}. <p>
369: *
370: * @throws Exception if an IOException occurs
371: */
372: private void openRAF() throws Exception {
373:
374: trace("openRAF(): entered.");
375:
376: raf = new RandomAccessFile(f, "rw");
377:
378: trace("openRAF(): got new 'rw' mode " + raf);
379: }
380:
381: /**
382: * Retrieves the last written hearbeat timestamp from
383: * this object's lock file. If this object's lock file
384: * does not exist, <code>Long.MIN_VALUE</code> (the earliest
385: * time representable as a long in Java) is retrieved. <p>
386: *
387: * @throws Exception if an error occurs while reading the hearbeat
388: * timestamp from this object's lock file.
389: * @return the hearbeat timestamp from this object's lock file,
390: * as a <code>long</code> value or, if this object's lock
391: * file does not exist, Long.MIN_VALUE, the earliest time
392: * representable as a long in Java,
393: */
394: private long readHeartbeat() throws Exception {
395:
396: DataInputStream dis;
397: long heartbeat;
398:
399: heartbeat = Long.MIN_VALUE;
400:
401: String mn = "readHeartbeat(): ";
402: String path = "lock file [" + cpath + "]";
403:
404: trace(mn + "entered.");
405:
406: if (!f.exists()) {
407: trace(mn + path + " does not exist. Return '" + heartbeat
408: + "'");
409:
410: return heartbeat;
411: }
412:
413: dis = new DataInputStream(new FileInputStream(f));
414:
415: trace(mn + " got new " + dis);
416:
417: for (int i = 0; i < MAGIC.length; i++) {
418: if (MAGIC[i] != dis.readByte()) {
419: trace(mn + path + " is not lock file. Return '"
420: + heartbeat + "'");
421:
422: return heartbeat;
423: }
424: }
425:
426: heartbeat = dis.readLong();
427:
428: trace(mn + " read: ["
429: + HsqlDateTime.getTimestampString(heartbeat) + "]");
430: dis.close();
431: trace(mn + " closed " + dis);
432:
433: return heartbeat;
434: }
435:
436: /**
437: * Provides any specialized release actions for the tryRelease()
438: * method. <p>
439: *
440: * @return true if there are no specialized release
441: * actions performed or they succeed,
442: * else false
443: * @throws Exception if a situation is encountered that absolutely
444: * prevents the status of the lock condtion
445: * to be determined. (e.g. an IO exception
446: * occurs here).
447: */
448: protected boolean releaseImpl() throws Exception {
449:
450: trace("releaseImpl(): no action: returning true");
451:
452: return true;
453: }
454:
455: /** Schedules the lock heartbeat task. */
456: private void startHeartbeat() {
457:
458: Runnable r;
459:
460: trace("startHeartbeat(): entered.");
461:
462: if (timerTask == null || HsqlTimer.isCancelled(timerTask)) {
463: r = new HeartbeatRunner();
464:
465: // now, periodic at HEARTBEAT_INTERVAL, running this, fixed rate
466: timerTask = timer.schedulePeriodicallyAfter(0,
467: HEARTBEAT_INTERVAL, r, true);
468:
469: trace("startHeartbeat(): heartbeat task scheduled.");
470: }
471:
472: trace("startHeartbeat(): exited.");
473: }
474:
475: /** Cancels the lock heartbeat task. */
476: private void stopHeartbeat() {
477:
478: String mn = "stopHeartbeat(): ";
479:
480: trace(mn + "entered");
481:
482: if (timerTask != null && !HsqlTimer.isCancelled(timerTask)) {
483: HsqlTimer.cancel(timerTask);
484:
485: timerTask = null;
486: }
487:
488: trace(mn + "exited");
489: }
490:
491: /**
492: * Writes a magic value to this object's lock file that distiguishes
493: * it as an HSQLDB lock file.
494: *
495: * @throws Exception if the magic value cannot be written to
496: * the lock file
497: */
498: private void writeMagic() throws Exception {
499:
500: String mn = "writeMagic(): ";
501: String path = "lock file [" + cpath + "]";
502:
503: trace(mn + "entered.");
504: trace(mn + "raf.seek(0)");
505: raf.seek(0);
506: trace(mn + "raf.write(byte[])");
507: raf.write(MAGIC);
508: trace(mn + "wrote [\"HSQLLOCK\".getBytes()] to " + path);
509: }
510:
511: /**
512: * Writes the current hearbeat timestamp value to this
513: * object's lock file. <p>
514: *
515: * @throws Exception if the current heartbeat timestamp value
516: * cannot be written
517: */
518: private void writeHeartbeat() throws Exception {
519:
520: long time;
521: String mn = "writeHeartbeat(): ";
522: String path = "lock file [" + cpath + "]";
523:
524: trace(mn + "entered.");
525:
526: time = System.currentTimeMillis();
527:
528: trace(mn + "raf.seek(" + MAGIC.length + ")");
529: raf.seek(MAGIC.length);
530: trace(mn + "raf.writeLong(" + time + ")");
531: raf.writeLong(time);
532: trace(mn + "wrote [" + time + "] to " + path);
533: }
534:
535: /**
536: * Retrieves a <code>LockFile</code> instance, initialized with a
537: * <code>File</code> object whose path is the one specified by
538: * the <code>path</code> argument. <p>
539: *
540: * @return a <code>LockFile</code> instance initialized with a
541: * <code>File</code> object whose path is the one specified
542: * by the <code>path</code> argument.
543: * @param path the path of the <code>File</code> object with
544: * which the retrieved <code>LockFile</code>
545: * object is to be initialized
546: */
547: public static LockFile newLockFile(String path) throws Exception {
548:
549: File f;
550: LockFile lf;
551: Class c;
552:
553: c = null;
554:
555: try {
556: Class.forName("java.nio.channels.FileLock");
557:
558: c = Class.forName("org.hsqldb.persist.NIOLockFile");
559: lf = (LockFile) c.newInstance();
560: } catch (Exception e) {
561: lf = new LockFile();
562: }
563:
564: f = new File(path);
565:
566: FileUtil.makeParentDirectories(f);
567: lf.setFile(f);
568:
569: return lf;
570: }
571:
572: public static LockFile newLockFileLock(String path)
573: throws HsqlException {
574:
575: LockFile lf = null;
576:
577: try {
578: lf = LockFile.newLockFile(path + ".lck");
579: } catch (Exception e) {
580: throw Trace.error(Trace.FILE_IO_ERROR, e.toString());
581: }
582:
583: boolean locked = false;
584: String msg = "";
585:
586: try {
587: locked = lf.tryLock();
588: } catch (Exception e) {
589:
590: // e.printStackTrace();
591: msg = e.toString();
592: }
593:
594: if (!locked) {
595: throw Trace.error(Trace.DATABASE_ALREADY_IN_USE, lf + ": "
596: + msg);
597: }
598:
599: return lf;
600: }
601:
602: /**
603: * Tests whether some other object is "equal to" this one.
604: *
605: * An object is considered equal to a <code>LockFile</code> object iff it
606: * is not null, it is an instance of <code>LockFile</code> and either it's
607: * the identical instance or it has the same lock file. More formally,
608: * is is considered equal iff it is not null, it is an instance of
609: * <code>LockFile</code>, and the expression: <p>
610: *
611: * <pre>
612: * this == other ||
613: * this.f == null ? other.f == null : this.f.equals(other.f);
614: * </pre>
615: *
616: * yeilds true. <p>
617: *
618: * @param obj the reference object with which to compare.
619: * @return <code>true</code> if this object is equal to
620: * the <code>obj</code> argument;
621: * <code>false</code> otherwise.
622: * @see #hashCode
623: */
624: public boolean equals(Object obj) {
625:
626: // do faster tests first
627: if (this == obj) {
628: return true;
629: } else if (obj instanceof LockFile) {
630: LockFile that = (LockFile) obj;
631:
632: return (f == null) ? that.f == null : f.equals(that.f);
633: } else {
634: return false;
635: }
636: }
637:
638: /**
639: * Retrieves, as a String, the canonical path of this object's lock file.
640: *
641: * @return the canonical path of this object's lock file.
642: */
643: public String getCanonicalPath() {
644: return cpath;
645: }
646:
647: /**
648: * Retrieves the hash code value for this object.
649: *
650: * The value is zero if the <code>File</code> object attribute
651: * <code>f</code> is <code>null</code>, else it is the <code>hashCode</code>
652: * of <code>f</code>. That is, two <code>LockFile</code>
653: * objects have the same <code>hashCode</code> value if they have the
654: * same lock file. <p>
655: *
656: * @return a hash code value for this object.
657: * @see #equals(java.lang.Object)
658: */
659: public int hashCode() {
660: return f == null ? 0 : f.hashCode();
661: }
662:
663: /**
664: * Retrieves whether this object has successfully obtained and is
665: * still currently holding (has not yet released) a cooperative
666: * lock condition on its lock file. <p>
667: *
668: * <b>Note:</b> Due to the retrictions placed on the JVM by
669: * platform-independence, it is very possible to successfully
670: * obtain and hold a cooperative lock on a lock file and yet for
671: * the lock to become invalid while held. <p>
672: *
673: * For instance, under JVMs with no <code>java.nio</code> package or
674: * operating systems that cannot live up to the contracts set forth for
675: * {@link java.nio.channels.FileLock FileLock}, it is quite possible
676: * for another process or even an uncooperative bit of code running
677: * in the same JVM to delete or overwrite the lock file while
678: * this object holds a lock on it. <p>
679: *
680: * Because of this, the isValid() method is provided in the public
681: * interface in order to allow clients to detect such situations. <p>
682: *
683: * @return true iff this object has successfully obtained
684: * and is currently holding (has not yet released)
685: * a lock on its lock file
686: * @see #isValid
687: */
688: public boolean isLocked() {
689: return locked;
690: }
691:
692: /**
693: * Retrieves whether there is potentially already a cooperative lock,
694: * operating system lock or some other situation preventing
695: * a cooperative lock condition from being aquired, relative to the
696: * specified path.
697: *
698: * @param path the path to test
699: */
700: public static boolean isLocked(String path) {
701:
702: FileInputStream fis = null;
703:
704: try {
705: LockFile lf = LockFile.newLockFile(path);
706:
707: lf.checkHeartbeat();
708:
709: if (lf.f.exists() && lf.f.isFile()) {
710: fis = new FileInputStream(lf.f);
711:
712: fis.read();
713: }
714:
715: return false;
716: } catch (Exception e) {
717: } finally {
718: if (fis != null) {
719: try {
720: fis.close();
721: } catch (java.io.IOException e) {
722: }
723: }
724: }
725:
726: return true;
727: }
728:
729: /**
730: * Retrieves whether this object holds a valid lock on its lock file. <p>
731: *
732: * More formally, this method retrieves true iff: <p>
733: *
734: * <pre>
735: * isLocked() &&
736: * f != null &&
737: * f.exists() &&
738: * raf != null
739: * </pre>
740: *
741: * @return true iff this object holds a valid lock on its
742: * lock file.
743: */
744: public boolean isValid() {
745: return isLocked() && f != null && f.exists() && raf != null;
746: }
747:
748: /**
749: * For internal use only. <p>
750: *
751: * This Runnable class provides the implementation for the timed task
752: * that periodically writes out a heartbeat timestamp to the lock file.<p>
753: */
754: protected class HeartbeatRunner implements Runnable {
755:
756: public void run() {
757:
758: try {
759: trace("HeartbeatRunner.run(): writeHeartbeat()");
760: writeHeartbeat();
761: } catch (Throwable t) {
762: trace("HeartbeatRunner.run(): caught Throwable: " + t);
763: }
764: }
765: }
766:
767: /**
768: * Retrieves a String representation of this object. <p>
769: *
770: * The String is of the form: <p>
771: *
772: * <pre>
773: * super.toString() +
774: * "[file=" + getAbsolutePath() +
775: * ", exists=" + f.exists() +
776: * ", locked=" + isLocked() +
777: * ", valid=" + isValid() +
778: * ", " + toStringImpl() +
779: * "]";
780: * </pre>
781: * @return a String representation of this object.
782: * @see #toStringImpl
783: */
784: public String toString() {
785:
786: return super .toString() + "[file =" + cpath + ", exists="
787: + f.exists() + ", locked=" + isLocked() + ", valid="
788: + isValid() + ", " + toStringImpl() + "]";
789: }
790:
791: /**
792: * Retrieves an implementation-specific tail value for the
793: * toString() method. <p>
794: *
795: * The default implementation returns the empty string.
796: * @return an implementation-specific tail value for the toString() method
797: * @see #toString
798: */
799: protected String toStringImpl() {
800: return "";
801: }
802:
803: /**
804: * Attempts, if not already held, to obtain a cooperative lock condition
805: * on this object's lock file. <p>
806: *
807: * @throws Exception if an error occurs that absolutely prevents the lock
808: * status of the lock condition from being determined
809: * (e.g. an unhandled file I/O error).
810: * @return <code>true</code> if this object already holds a lock or
811: * the lock was obtained successfully, else
812: * <code>false</code>
813: */
814: public boolean tryLock() throws Exception {
815:
816: String mn = "tryLock(): ";
817:
818: trace(mn + "entered.");
819:
820: if (locked) {
821: trace(mn
822: + " lock already held. Returning true immediately.");
823:
824: return true;
825: }
826:
827: checkHeartbeat();
828:
829: // Alternatively, we could give ourselves a second try,
830: // raising our chances of success in the rare case that the
831: // last locker terminiated abruptly just less than
832: // HEARTBEAT_INTERVAL ago.
833: //
834: // try {
835: // checkHeartbeat();
836: // } catch (Exception e) {
837: // try {
838: // Thread.sleep(HEARTBEAT_INTERVAL);
839: // } catch (Exception e2) {}
840: // checkHeartbeat();
841: // }
842: openRAF();
843:
844: locked = lockImpl();
845:
846: if (locked) {
847: writeMagic();
848: startHeartbeat();
849:
850: try {
851:
852: // attempt to ensure that tryRelease() gets called if/when
853: // the VM shuts down, just in case this object has not yet
854: // been garbage-collected or explicitly released.
855: JavaSystem.runFinalizers();
856: trace(mn
857: + "success for System.runFinalizersOnExit(true)");
858: } catch (Exception e) {
859: trace(mn + e.toString());
860: }
861: } else {
862: try {
863: releaseImpl();
864: closeRAF();
865: } catch (Exception e) {
866: trace(mn + e.toString());
867: }
868: }
869:
870: trace(mn + "ran to completion. Returning " + locked);
871:
872: return locked;
873: }
874:
875: /**
876: * Attempts to release any cooperative lock condition this object
877: * may have on its lock file. <p>
878: *
879: * @throws Exception if an error occurs that absolutely prevents
880: * the status of the lock condition from
881: * being determined (e.g. an unhandled file
882: * I/O exception).
883: * @return <code>true</code> if this object does not hold a
884: * lock or the lock is released successfully,
885: * else <code>false</code>.
886: */
887: public boolean tryRelease() throws Exception {
888:
889: String mn = "tryRelease(): ";
890: String path;
891:
892: trace(mn + "entered.");
893:
894: boolean released = !locked;
895:
896: if (released) {
897: trace(mn + "No lock held. Returning true immediately");
898:
899: return true;
900: }
901:
902: try {
903: released = releaseImpl();
904: } catch (Exception e) {
905: trace(mn + e);
906: }
907:
908: if (!released) {
909: trace(mn
910: + "releaseImpl() failed. Returning false immediately.");
911:
912: return false;
913: }
914:
915: trace(mn + "releaseImpl() succeeded.");
916: stopHeartbeat();
917: closeRAF();
918:
919: // without a small delay, the following delete may occasionally fail
920: // and return false on some systems, when really it should succeed
921: // and return true.
922: trace(mn + "Starting Thread.sleep(100).");
923:
924: try {
925: Thread.sleep(100);
926: } catch (Exception e) {
927: trace(mn + e.toString());
928: }
929:
930: trace(mn + "Finished Thread.sleep(100).");
931:
932: path = "[" + cpath + "]";
933:
934: if (f.exists()) {
935: trace(mn + path + " exists.");
936:
937: released = f.delete();
938:
939: trace(mn + path + (released ? "" : "not") + " deleted.");
940:
941: if (f.exists()) {
942: trace(mn + " WARNING!: " + path + "still exists.");
943: }
944: }
945:
946: locked = !released;
947:
948: trace(mn + "ran to completion. Returning " + released);
949:
950: return released;
951: }
952:
953: /**
954: * Prints tracing information and the value of the specified object
955: *
956: * @param o the value to print
957: */
958: protected void trace(Object o) {
959:
960: if (Trace.TRACE) {
961: Trace.printSystemOut("[" + super .toString() + "]: " + o);
962: }
963: }
964:
965: /**
966: * Attempts to release any lock condition this object may have on its
967: * lock file. <p>
968: *
969: * @throws Throwable if this object encounters an unhandled exception
970: * trying to release the lock condition,
971: * if any, that it has on its lock file.
972: */
973: protected void finalize() throws Throwable {
974: trace("finalize(): calling tryRelease()");
975: tryRelease();
976: }
977: }
|