Adding support for shared file locking via a new class for file locking, FSLockWithShared. This will eventually take over for FSLock, the current file locking class - I'll work with Aaron to merge the tribble code that uses FSLock right now.

FYI: creating an exclusive lock on a file that does not exist will create that file as an empty file, and will NOT delete that file after the program terminates. So watch out if it's possible that the file you're locking does not exist - could end up leaving extra files that confuse users.  



git-svn-id: file:///humgen/gsa-scr1/gsa-engineering/svn_contents/trunk@3795 348d0f76-0448-11de-a6fe-93d51630548a
This commit is contained in:
bthomas 2010-07-14 20:45:51 +00:00
parent a8caa20378
commit f65cba6b9a
2 changed files with 174 additions and 66 deletions

View File

@ -30,11 +30,9 @@ import net.sf.picard.reference.FastaSequenceIndexBuilder;
import net.sf.picard.sam.CreateSequenceDictionary;
import net.sf.picard.reference.IndexedFastaSequenceFile;
import net.sf.picard.reference.FastaSequenceIndex;
import org.broadinstitute.sting.utils.file.FSLockWithShared;
import java.io.File;
import java.io.RandomAccessFile;
import java.nio.channels.FileChannel;
import java.nio.channels.FileLock;
/**
* Loads reference data from fasta file
@ -52,39 +50,35 @@ public class ReferenceDataSource implements ReferenceDataSourceProgressListener
*/
public ReferenceDataSource(File fastaFile) {
File indexFile = new File(fastaFile.getAbsolutePath() + ".fai");
File dictFile = new File(fastaFile.getAbsolutePath().replace(".fasta", ".dict"));
File dictFile;
if (fastaFile.getAbsolutePath().endsWith("fa")) {
dictFile = new File(fastaFile.getAbsolutePath().replace(".fa", ".dict"));
}
else
dictFile = new File(fastaFile.getAbsolutePath().replace(".fasta", ".dict"));
/*
if index file does not exist, create it manually
*/
if (!indexFile.exists()) {
logger.info(String.format("Index file %s does not exist. Trying to create it now.", indexFile.getAbsolutePath()));
FileChannel indexChannel;
FileLock indexLock;
FSLockWithShared indexLock = new FSLockWithShared(indexFile);
try {
// get exclusive lock
indexChannel = new RandomAccessFile(indexFile, "rw").getChannel();
if ((indexLock = indexChannel.tryLock(0, Long.MAX_VALUE, false)) == null)
if (!indexLock.exclusiveLock())
throw new StingException("Index file could not be written because a lock could not be obtained." +
"If you are running multiple instances of GATK, another process is probably creating this " +
"file now. Please wait until it is finished and try again.");
FastaSequenceIndexBuilder faiBuilder = new FastaSequenceIndexBuilder(fastaFile, this);
FastaSequenceIndex sequenceIndex = faiBuilder.createIndex();
FastaSequenceIndexBuilder.saveAsFaiFile(sequenceIndex, indexFile);
// unlock
try {
if (indexLock != null)
indexLock.release();
if (indexChannel != null)
indexChannel.close();
}
catch (Exception e) {
throw new StingException("An error occurred while unlocking file:" + indexFile.getAbsolutePath(), e);
}
}
catch (Exception e) {
throw new StingException("Index file does not exist and could not be created. See error below.", e);
}
finally {
indexLock.unlock();
}
}
/*
@ -94,36 +88,41 @@ public class ReferenceDataSource implements ReferenceDataSourceProgressListener
* This has been filed in trac as (PIC-370) Want programmatic interface to CreateSequenceDictionary
*/
if (!dictFile.exists()) {
logger.info(String.format("Index file %s does not exist. Trying to create it now.", indexFile.getAbsolutePath()));
FileChannel dictChannel;
FileLock dictLock;
try {
// get exclusive lock
dictChannel = new RandomAccessFile(indexFile, "rw").getChannel();
if ((dictLock = dictChannel.tryLock(0, Long.MAX_VALUE, false)) == null)
logger.info(String.format("Dict file %s does not exist. Trying to create it now.", dictFile.getAbsolutePath()));
/*
* Please note another hack here: we have to create a temporary file b/c CreateSequenceDictionary cannot
* create a dictionary file if that file is locked.
*/
// get read lock on dict file so nobody else can read it
FSLockWithShared dictLock = new FSLockWithShared(dictFile);
try {
// get shared lock on dict file so nobody else can start creating it
if (!dictLock.exclusiveLock())
throw new StingException("Dictionary file could not be written because a lock could not be obtained." +
"If you are running multiple instances of GATK, another process is probably creating this " +
"file now. Please wait until it is finished and try again.");
// dict will be written to random temporary file in same directory (see note above)
File tempFile = File.createTempFile("dict", null, dictFile.getParentFile());
tempFile.deleteOnExit();
// create dictionary by calling main routine. Temporary fix - see comment above.
String args[] = {String.format("r=%s", fastaFile.getAbsolutePath()),
String.format("o=%s", dictFile.getAbsolutePath())};
String.format("o=%s", tempFile.getAbsolutePath())};
new CreateSequenceDictionary().instanceMain(args);
// unlock
try {
if (dictLock != null)
dictLock.release();
if (dictChannel != null)
dictChannel.close();
}
catch (Exception e) {
throw new StingException("An error occurred while unlocking file:" + indexFile.getAbsolutePath(), e);
}
if (!tempFile.renameTo(dictFile))
throw new StingException("Error transferring temp file to dict file");
}
catch (Exception e) {
throw new StingException("Dictionary file does not exist and could not be created. See error below.", e);
}
finally {
dictLock.unlock();
}
}
/*
@ -133,46 +132,25 @@ public class ReferenceDataSource implements ReferenceDataSourceProgressListener
* but is incomplete). To avoid this, obtain shared locks on both files before creating IndexedFastaSequenceFile.
*/
FileChannel dictChannel;
FileChannel indexChannel;
FileLock dictLock;
FileLock indexLock;
FSLockWithShared dictLock = new FSLockWithShared(dictFile);
FSLockWithShared indexLock = new FSLockWithShared(indexFile);
try {
// set up dictionary and index locks
// channel is read only and lock is shared (third argument is true)
dictChannel = new RandomAccessFile(dictFile, "r").getChannel();
if ((dictLock = dictChannel.tryLock(0, Long.MAX_VALUE, true)) == null) {
if (!dictLock.sharedLock()) {
throw new StingException("Could not open dictionary file because a lock could not be obtained.");
}
indexChannel = new RandomAccessFile(indexFile, "r").getChannel();
if ((indexLock = indexChannel.tryLock(0, Long.MAX_VALUE, true)) == null) {
if (!indexLock.sharedLock()) {
throw new StingException("Could not open dictionary file because a lock could not be obtained.");
}
index = new IndexedFastaSequenceFile(fastaFile);
// unlock/close
try {
if (dictLock != null)
dictLock.release();
if (dictChannel != null)
dictChannel.close();
}
catch (Exception e) {
throw new StingException("An error occurred while unlocking file:" + dictFile.getAbsolutePath(), e);
}
try {
if (indexLock != null)
indexLock.release();
if (indexChannel != null)
indexChannel.close();
}
catch (Exception e) {
throw new StingException("An error occurred while unlocking file:" + indexFile.getAbsolutePath(), e);
}
}
catch (Exception e) {
throw new StingException(String.format("Error reading fasta file %s. See stack trace below.", fastaFile.getAbsolutePath()), e);
throw new StingException(String.format("Error reading fasta file %s.", fastaFile.getAbsolutePath()), e);
}
finally {
dictLock.unlock();
indexLock.unlock();
}
}

View File

@ -0,0 +1,130 @@
package org.broadinstitute.sting.utils.file;
import org.apache.log4j.Logger;
import org.broadinstitute.sting.utils.StingException;
import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.channels.ClosedChannelException;
import java.nio.channels.FileChannel;
import java.nio.channels.FileLock;
import java.nio.channels.OverlappingFileLockException;
/**
* a quick implementation of a file based lock, using the Java NIO classes
*/
public class FSLockWithShared {
// connect to the logger
private final static Logger logger = Logger.getLogger(FSLockWithShared.class);
// the file we're attempting to lock
private final File file;
// the file lock
private FileLock lock = null;
// the file channel we open
private FileChannel channel = null;
/**
* create a file system, given a base file to which a lock string gets appended.
* @param baseFile File descriptor of file to lock
*/
public FSLockWithShared(File baseFile) {
file = baseFile;
}
/**
* Get a shared (read) lock on a file
* Cannot get shared lock if it does not exist
* @return boolean true if we obtained a lock
*/
public boolean sharedLock() {
// get read-only file channel
try {
channel = new RandomAccessFile(file, "r").getChannel();
}
catch (IOException e) {
logger.debug("Unable to lock file (could not open read only file channel)");
return false;
}
// get shared lock (third argument is true)
try {
lock = channel.tryLock(0, Long.MAX_VALUE, true);
if (lock == null) {
logger.debug("Unable to lock file because there is already a lock active.");
return false;
}
}
catch (ClosedChannelException e) {
logger.debug("Unable to lock file because the file channel is closed.");
return false;
}
catch (OverlappingFileLockException e) {
logger.debug("Unable to lock file because you already have a lock on this file.");
return false;
}
catch (IOException e) {
logger.debug("Unable to lock file (due to IO exception)");
return false;
}
return true;
}
/**
* Get an exclusive lock on a file
* @return boolean true if we obtained a lock
*/
public boolean exclusiveLock() {
// read/write file channel is necessary for exclusive lock
try {
channel = new RandomAccessFile(file, "rw").getChannel();
}
catch (Exception e) {
logger.debug("Unable to lock file (could not open read/write file channel)");
// do we need to worry about deleting file here? Does RandomAccessFile will only create file if successful?
return false;
}
// get exclusive lock (third argument is false)
try {
lock = channel.tryLock(0, Long.MAX_VALUE, false);
if (lock == null) {
logger.debug("Unable to lock file because there is already a lock active.");
return false;
}
else return true;
}
catch (ClosedChannelException e) {
logger.debug("Unable to lock file because the file channel is closed.");
return false;
}
catch (OverlappingFileLockException e) {
logger.debug("Unable to lock file because you already have a lock on this file.");
return false;
}
catch (IOException e) {
logger.debug("Unable to lock file (due to IO exception)");
return false;
}
}
/**
* unlock the file
*
* note: this allows unlocking a file that failed to lock (no required user checks on null locks).
*/
public void unlock() {
try {
if (lock != null)
lock.release();
if (channel != null)
channel.close();
}
catch (Exception e) {
throw new StingException("An error occurred while unlocking file", e);
}
}
}