package org.broadinstitute.sting.utils.threading; import org.apache.log4j.Logger; import org.apache.lucene.store.*; import org.broadinstitute.sting.utils.exceptions.ReviewedStingException; import org.broadinstitute.sting.utils.exceptions.UserException; import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; import java.io.RandomAccessFile; import java.nio.channels.*; /** * User: depristo * Date: 1/19/11 * Time: 8:24 AM * * A reentrant lock for a shared file common file in the file system. Relies on a a Lucene SimpleFSLock * to manage on disk file locking. */ public class SharedFileLock extends ClosableReentrantLock { // todo -- kinda gross inheritance. The super lock is never used private static Logger logger = Logger.getLogger(SharedFileLock.class); private static final String VERIFY_HOST = System.getProperty("verify.host", "gsa1"); private static final boolean VERIFY = false; private static final int VERIFY_PORT = 5050; // 5 minutes => 360 seconds of trying -> failure protected static final int DEFAULT_N_TRIES = 1000; protected static final long DEFAULT_MILLISECONDS_PER_TRY = 360; /** The file we are locking */ private final File file; private final LockFactory lockFactory; private Lock fileLock = null; /** * A counter that indicates the number of 'locks' on this file. * If locks == 2, then two unlocks are required * before any resources are freed. */ int fileLockReentrantCounter = 0; // type of locking private final int nRetries; private final long milliSecPerTry; /** * Create a SharedFileThreadSafeLock object locking the file * @param file */ public SharedFileLock(File file, int nRetries, long milliSecPerTry, int ID) { super(); this.file = file; this.nRetries = nRetries; this.milliSecPerTry = milliSecPerTry; File lockDir = new File(file.getParent() == null ? "./" : file.getParent()); try { LockFactory factory = new SimpleFSLockFactory(lockDir); if ( VERIFY ) { // don't forget to start up the VerifyLockServer this.lockFactory = new VerifyingLockFactory((byte)ID, factory, VERIFY_HOST, VERIFY_PORT); } else { this.lockFactory = factory; } } catch (IOException e) { throw new UserException.CouldNotCreateOutputFile(lockDir, "Could not create coordination file locking directory " + lockDir, e); } } public SharedFileLock(File file, int ID) { this(file, DEFAULT_N_TRIES, DEFAULT_MILLISECONDS_PER_TRY, ID); } @Override public void close() { if ( ownsLock() ) throw new ReviewedStingException("closing SharedFileLock while still owned: ownership count " + fileLockReentrantCounter); } @Override public int getHoldCount() { return fileLockReentrantCounter; } @Override public boolean ownsLock() { return fileLockReentrantCounter > 0; } // ------------------------------------------------------------------------------------------ // // workhorse routines -- acquiring file locks // // ------------------------------------------------------------------------------------------ private boolean obtainFileLock() throws IOException { // annoying bug work around for verifylockserver if ( VERIFY ) try { return fileLock.obtain(1); } catch ( LockObtainFailedException e ) { return false; } else return fileLock.obtain(); } /** * Two stage [threading then file] locking mechanism. Reenterant in that multiple lock calls will be * unwound appropriately. Uses file channel lock *after* thread locking. */ @Override public void lock() { if ( SharedFileThreadSafeLock.DEBUG ) logger.warn(" lock() " + Thread.currentThread().getName() + ", fileLockReentrantCounter = " + fileLockReentrantCounter); if ( fileLockReentrantCounter++ == 0 ) { // Precondition -- lock is always null while we don't have a lock if ( fileLock != null ) throw new ReviewedStingException("BUG: lock() function called when a lock already is owned!"); int i = 1; fileLock = lockFactory.makeLock(file.getName() + ".lock"); try { boolean obtained = obtainFileLock(); // todo -- maybe use intrinsic lock features for ( ; ! obtained && i < nRetries; i++ ) { try { //logger.warn("tryLock failed on try " + i + ", waiting " + milliSecPerTry + " millseconds for retry"); Thread.sleep(milliSecPerTry); } catch ( InterruptedException e ) { throw new UserException("SharedFileThreadSafeLock interrupted during wait for file lock", e); } obtained = obtainFileLock(); // gross workaround for error in verify server } if ( i > 1 ) logger.warn("tryLock required " + i + " tries before completing, waited " + i * milliSecPerTry + " millseconds"); if ( ! obtained ) { fileLock = null; // filelock == null -> we never managed to acquire the lock! throw new UserException("SharedFileThreadSafeLock failed to obtain the lock after " + nRetries + " attempts"); } if ( SharedFileThreadSafeLock.DEBUG ) logger.warn(" lock() " + Thread.currentThread().getName() + ", obtained = " + obtained + ", tries = " + i); } catch (IOException e) { fileLock = null; throw new ReviewedStingException("Coordination file could not be created because a lock could not be obtained.", e); } } } @Override public void unlock() { // update for reentrant unlocking if ( fileLock == null ) throw new ReviewedStingException("BUG: file lock is null -- file lock was not obtained"); if ( fileLockReentrantCounter <= 0 ) throw new ReviewedStingException("BUG: file lock counter < 0"); // this unlock counts as 1 unlock. If this is our last unlock, actually do something if ( SharedFileThreadSafeLock.DEBUG ) logger.warn(" unlock() " + Thread.currentThread().getName() + ", count = " + fileLockReentrantCounter); if ( --fileLockReentrantCounter == 0 ) { try { if ( ! fileLock.isLocked() ) throw new ReviewedStingException("BUG: call to unlock() when we don't have a valid lock!"); fileLock.release(); if ( SharedFileThreadSafeLock.DEBUG ) logger.warn(" unlock() " + Thread.currentThread().getName() + ", actually releasing"); } catch ( IOException e ) { throw new ReviewedStingException("Could not free file lock on file " + file, e); } finally { // make sure we null out the filelock, regardless of our state fileLock = null; } } else { if ( SharedFileThreadSafeLock.DEBUG ) logger.warn(" unlock() " + Thread.currentThread().getName() + ", skipping, count = " + fileLockReentrantCounter); } } }