/*
Copyright (C) SYSTAP, LLC DBA Blazegraph 2006-2016. All rights reserved.
Contact:
SYSTAP, LLC DBA Blazegraph
2501 Calvert ST NW #106
Washington, DC 20008
licenses@blazegraph.com
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; version 2 of the License.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
/*
* Created on Sep 19, 2008
*/
package com.bigdata.io;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.channels.FileChannel;
import java.nio.channels.FileLock;
import java.util.UUID;
import org.apache.log4j.Logger;
import com.bigdata.journal.AbstractJournal;
import com.bigdata.journal.FileMetadata;
import com.bigdata.journal.Options;
import com.bigdata.resources.StoreManager;
/**
* Utility methods for managing exlusive {@link FileLock}s and advisory locks
* depending on what is supported by the platform, file access mode, and volume
* on which the file resides.
*
* @author <a href="mailto:thompsonbry@users.sourceforge.net">Bryan Thompson</a>
* @version $Id$
*
* @deprecated This can lead to trouble. Use a {@link FileLock} if supported and
* otherwise proceed without a {@link FileLock}.
*/
public class FileLockUtility {
protected static final Logger log = Logger.getLogger(FileLockUtility.class);
protected static final boolean INFO = log.isInfoEnabled();
protected static final boolean DEBUG = log.isDebugEnabled();
/**
* Create/open the file and obtain an exclusive lock.
* <p>
* A {@link FileLock} will be used when supported and requested. An advisory
* lock will be used if <i>useFileLock == false</code>, if the <i>fileMode</i>
* is read-only. If tryLock() returns <code>null</code> then the lock
* exists and this request will fail. However, if
* {@link FileChannel#tryLock()} throws an {@link IOException} then the
* underlying platform does not support {@link FileLock} for the named file
* (memory mapped files, read-only files, and NFS mounted files can all have
* this problem) and we will attempt to acquire an advisory lock instead.
*
* <strong>Advisory locks are NOT visible to other applications</strong>
*
* <p>
* Do NOT request a {@link FileLock} if you are going to use a memory-mapped
* buffer. The JDK cautions that these things do not play well together on
* some platforms.
*
* @param file
* The file.
* @param fileMode
* The file mode for
* {@link RandomAccessFile#RandomAccessFile(File, String)}
* @param useFileLock
* <code>true</code> if {@link FileChannel#tryLock()} should be
* attempted. when <code>false</code> only an advisory lock
* will be sought.
*
* @return The {@link RandomAccessFile}
*
* @throws IOException
* If the file could not be opened or someone already holds a
* lock for that file.
*
* @see FileMetadata#acquireAdvisoryLock(File)
*
* @see Options#FILE_LOCK_ENABLED
*
* @todo we really don't need locks for temporary files.
*
* @todo handle lock files during {@link StoreManager} startup.
*
* @todo if you use a {@link FileLock} to open a file then you are
* protected.
*
* @todo this ignores locks for read only file modes. in fact we should
* create the file with a read-only marker and allow more than one
* process to obtain that lock.
*/
public static RandomAccessFile openFile(File file, String fileMode,
boolean useFileLock) throws IOException {
final boolean readOnly = "r".equals(fileMode);
final RandomAccessFile raf = new RandomAccessFile(file, fileMode);
if(readOnly) return raf;
// Note: a System property.
final boolean fileLockEnabled = Boolean.parseBoolean(System
.getProperty(Options.FILE_LOCK_ENABLED,
Options.DEFAULT_FILE_LOCK_ENABLED));
if (useFileLock && fileLockEnabled) {//bufferMode != BufferMode.Mapped) {
if (INFO)
log.info("Seeking exclusive lock: " + file.getAbsolutePath());
if (new File(file + ".lock").exists()) {
// reject if there is already an advisory lock for this file.
raf.close(); // but close file first!
throw new IOException("Advisory lock exists: "
+ file.getAbsolutePath());
}
try {
// seek a native platform exclusive file lock.
if (raf.getChannel().tryLock() != null) {
// got it.
return raf;
} else {
/*
* A null return indicates that someone else holds the lock.
*/
try {
raf.close();
} catch (Throwable t) {
// log and ignore.
log.error(t, t);
}
/*
* We were not able to get a lock on the file.
*/
throw new RuntimeException("Already locked: "
+ file.getAbsoluteFile());
}
} catch (IOException ex) {
/*
* The platform does not support FileLock (memory mapped files,
* read-only files, NFS mounted files all have this problem).
*/
log.warn("FileLock not supported: file="
+ file.getAbsolutePath() + " : " + ex);
return _acquireAdvisoryLock(raf,file);
}
} else {
/*
* FileLock not requested or explicitly disabled for the JVM.
*/
return _acquireAdvisoryLock(raf, file);
}
}
private static RandomAccessFile _acquireAdvisoryLock(RandomAccessFile raf,
File file) throws IOException {
try {
// seek an advisory lock.
if (acquireAdvisoryLock(file)) {
// obtained advisory lock.
return raf;
}
// someone else holds the advisory lock.
try {
raf.close();
} catch (IOException t) {
// log and ignore.
log.error(t, t);
}
throw new IOException("Advisory lock exists: " + file.getAbsolutePath());
} catch (IOException ex2) {
log.error("Error while seeking advisory lock: file=" + file.getAbsolutePath(), ex2);
try {
raf.close();
} catch (IOException t) {
// log and ignore.
log.error(t, t);
}
throw ex2;
}
}
/**
* Close the file and automatically releases the {@link FileLock} (if any)
* and removes the advisory lock for that file (if any).
* <p>
* Note: This method should be used in combination with
* {@link FileLockUtility#openFile(File, String, boolean)} in order to ensure that the
* optional advisory lock file is deleted when the file is closed. The
* purpose of the advisory lock file is to provide advisory locking file
* modes (read-only), platforms, or file systems (NFS) that do not support
* {@link FileLock}.
*
* @param file
* The file.
* @param raf
* The {@link RandomAccessFile}.
*
* @throws IOException
*/
public static void closeFile(File file, RandomAccessFile raf)
throws IOException {
if (file == null)
throw new IllegalArgumentException();
if (raf == null)
throw new IllegalArgumentException();
try {
if (raf.getChannel().isOpen()) {
/*
* close the file iff open.
*
* Note: a thread that is interrupted during an IO can cause the
* file to be closed asynchronously. This is handled by the
* disk-based store modes.
*/
raf.close();
}
} finally {
/*
* Remove the advisory lock (if present) regardles of whether the
* file is currently open (see note above).
*/
removeAdvisoryLock(file);
}
}
/**
* Creates an advisory lock file having the same basename as the given file
* with a <code>.lock</code> extension.
* <p>
* Note: This uses {@link File#createNewFile()} which is NOT advised for
* this purpose. However, {@link FileLock} does not work in some contexts so
* this is used as a fallback mechanism. We write a {@link UUID} into the
* advisory lock since Java does not have platform independent PIDs. That
* {@link UUID} allows us to tell whether the advisory lock file was created
* by this process or by another process.
* <p>
* Note: If a {@link Thread} is interrupted during an NIO operation then the
* {@link FileChannel} will be closed asynchronously. While this correctly
* releases a {@link FileLock} it does NOT cause our advisory lock file to
* be deleted. During a normal shutdown of an {@link AbstractJournal}, the
* advisory lock file is deleted by
* {@link #closeFile(File, RandomAccessFile)}. However, following an
* abnormal shutdown the advisory lock file MAY still exist and (assuming
* that {@link FileLock} is not working since we created an advisory lock in
* the first place) it MUST be removed by hand before the
* {@link AbstractJournal} can be reopened.
*
* @param file
* The given file.
*
* @return <code>true</code> if the advisory lock was created or exists
* and was created by this process. <code>false</code> if the
* advisory lock already exists and was created by another process.
*
* @throws IOException
* If there is a problem.
*
* @see #pid
*/
synchronized public static boolean acquireAdvisoryLock(File file)
throws IOException {
if (INFO)
log.info("Seeking advisory lock: " + file.getAbsolutePath());
final File lockFile = new File(file + ".lock");
if(lockFile.exists()) {
// check the signature in the lock file.
return isOurLockFile(lockFile);
}
if(!lockFile.createNewFile()) {
// someone else got there first.
return false;
}
{
final BufferedWriter w = new BufferedWriter(
new FileWriter(lockFile));
try {
w.write(pid);
w.write('\n');
w.flush();
} finally {
w.close();
}
}
if (INFO)
log.info("Created advisory lock: " + file.getAbsolutePath());
return true;
}
static public boolean isOurLockFile(File lockFile) throws IOException {
final BufferedReader r = new BufferedReader(new FileReader(lockFile));
try {
final String str = r.readLine();
if (pid.equals(str))
return true;
return false;
} finally {
r.close();
}
}
/**
* Removes the advisory lock for the file if it exists.
*
* @param file
* The file whose <code>.lock</code> file will be removed.
*
* @throws IOException
* if the lock file exists but does not belong to this process
* or can not be removed.
*
* @see #acquireAdvisoryLock(File)
*/
synchronized public static void removeAdvisoryLock(File file)
throws IOException {
final File lockFile = new File(file + ".lock");
// no advisory lock file.
if (!lockFile.exists()) return;
if (!isOurLockFile(lockFile)) {
throw new IOException("Not our lock file: " + lockFile.getAbsolutePath());
}
if (!lockFile.delete()) {
throw new IOException("Could not delete lock file: " + lockFile.getAbsolutePath());
}
}
/**
* Since Java does not have platform independent PIDs we use a static
* {@link UUID} to identify this process. This {@link UUID} gets written
* into all advisory lock files that the process creates. Another process
* should check the {@link UUID} in the advisory lock file and refuse to
* open the file if the {@link UUID} is not its own {@link UUID}.
*/
static String pid = UUID.randomUUID().toString();
}