org.infinispan.commons.util.concurrent.FileSystemLock Maven / Gradle / Ivy
package org.infinispan.commons.util.concurrent;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.channels.FileChannel;
import java.nio.channels.FileLock;
import java.nio.file.Path;
import org.infinispan.commons.util.Util;
import net.jcip.annotations.GuardedBy;
import net.jcip.annotations.ThreadSafe;
/**
* A simplified global lock backed by the file system.
*
* This implementation allows to control the access to external resource between different virtual machines. A file
* is created to control access, where subsequent tries do not succeed acquiring the lock as long as the file exists.
*
*
* Warning: This implementation does not have the semantics of a traditional {@link java.util.concurrent.locks.Lock}.
* Does not provide memory or visibility guarantees following the JMM.
*
*
* @since 15.0
*/
@ThreadSafe
public class FileSystemLock {
private final Path directory;
private final String name;
@GuardedBy("this")
private FileOutputStream globalLockFile;
@GuardedBy("this")
private FileLock globalLock;
/**
* Creates new instance of the lock.
*
* Creates a new file in the provided directory utilizing the lock's name.
*
*
* @param directory: Root directory to create the lock files.
* @param name: Uniquely identify the file name. If the names conflict, it means the lock is already held.
*/
public FileSystemLock(Path directory, String name) {
this.directory = directory;
this.name = name;
}
/**
* Tries to acquire the global lock.
*
* Creates a file in the provided directory with the lock's name. If the file already exists, the lock is held
* by another instance. The instance is not necessarily in the same virtual machine.
*
*
* @return true
if acquired the lock, false
, otherwise.
* @throws IOException: In case of failures creating the file.
* @see FileChannel#lock() Check the thrown exceptions.
*/
public synchronized boolean tryLock() throws IOException {
File lockFile = getLockFile();
return acquireGlobalLock(lockFile);
}
/**
* Unlocks the current instance if holding the global lock.
*
* This method only has an effect if the lock is hold by this instance. Effectively, the underlying file is deleted.
*
*/
public synchronized void unlock() {
if (globalLockFile != null) {
if (globalLock != null && globalLock.isValid())
Util.close(globalLock);
globalLock = null;
Util.close(globalLockFile);
getLockFile().delete();
}
}
/**
* Unsafely forces the current instance to hold the lock.
*
*
* This method bypasses the existing lock mechanism to delete the underlying file and acquire ownership over it.
* Use this method with caution! This method is useful in cases of hard crashes of the virtual machine where
* the lock was not released prior to shutdown.
*
*
* @throws IOException: In case of I/O errors while acquiring the lock.
* @see #tryLock() Exceptions list.
*/
public synchronized void unsafeLock() throws IOException {
boolean retry;
do {
getLockFile().delete();
retry = !tryLock();
} while (retry);
}
/**
* Check whether the current instance holds the global lock.
*
* @return true
in case the lock is held, false
, otherwise.
*/
public synchronized boolean isAcquired() {
return globalLock != null && globalLock.isValid();
}
private File getLockFile() {
return directory.resolve(lockFileName()).toFile();
}
private boolean acquireGlobalLock(File lockFile) throws IOException {
assert Thread.holdsLock(this);
lockFile.getParentFile().mkdirs();
globalLockFile = new FileOutputStream(lockFile);
globalLock = globalLockFile.getChannel().tryLock();
return globalLock.isValid();
}
private String lockFileName() {
return String.format("%s.lck", name);
}
@Override
public String toString() {
return "FileSystemLock{directory=" + directory + ", file=" + lockFileName() + "}";
}
}