// the lockFile must be a file separate from any data files. // It is created & deleted by this class, and will always have // size 0. sclass FileBasedLock implements AutoCloseable { File lockFile; double timeout = 60.0; // in seconds. refresh happens twice as often bool verbose; bool haveLock; java.util.Timer touchTimer; // what to put in the lock file when we create it settable S contentsForLockFile; *() {} *(File *lockFile) {} *(File *lockFile, double *timeout) {} // returns true iff lock was acquired (or kept) synchronized bool tryToLock() { if (haveLock) true; if (fileExists(lockFile)) { double age = fileAgeInSeconds(lockFile); double remaining = timeout-age; print("Lock file age: " + lockFile + ": " + iround(age) + " s" + (remaining <= 0 ? " - old, deleting" : " - please start again in " + nSeconds(iceil(remaining)))); if (remaining <= 0) { print("Deleting old lock file (program crashed?): " + lockFile + " (age: " + iround(age) + " seconds)"); deleteFile(lockFile); } } try { mkdirsForFile(lockFile); // This does the actual atomic file creation java.nio.file.Files.createFile(toPath(lockFile)); print("Created lock file: " + lockFile); // Optionally set the lock file's contents if (nempty(contentsForLockFile)) writeContents(); acquired(); true; } catch e { printExceptionShort("Can't lock", e); false; } } void writeContents { saveTextFileWithoutTemp(lockFile, unnull(contentsForLockFile)); } private void acquired { set haveLock; startTouchTimer(); } void forceLock ctex { print("Force-locking " + lockFile); writeContents(); acquired(); } S lockError() { ret "Couldn't aquire lock file: " + lockFile; } void lockOrFail { if (!tryToLock()) fail(lockError()); } synchronized void startTouchTimer { if (touchTimer != null) ret; double interval = timeout/2; touchTimer = doEvery(interval, r doTouch); if (verbose) print("Touch timer started for " + lockFile + " (" + interval + "s)"); } synchronized void doTouch() pcall { if (haveLock) { if (verbose) print("Touching lock file: " + lockFile); //touchExistingFile(lockFile); touchFile(lockFile); } } public synchronized void close() pcall { dispose touchTimer; if (haveLock) { haveLock = false; if (verbose) print("Deleting lock file: " + lockFile); deleteFile(lockFile); } } synchronized void _simulateCrash { dispose touchTimer; } void deleteOnExit { if (haveLock) lockFile.deleteOnExit(); } S actualContents() { ret loadTextFile(lockFile); } bool hasExpectedContents() { ret eq(unnull(contentsForLockFile), actualContents()); } }