org.modeshape.jcr.value.binary.FileLocks Maven / Gradle / Ivy
/*
* ModeShape (http://www.modeshape.org)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.modeshape.jcr.value.binary;
import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.channels.FileChannel;
import java.nio.channels.FileLock;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import org.modeshape.common.annotation.GuardedBy;
/**
* A utility class that represents read and write lock for files, which internally uses {@link FileLock} to coordinate file locks
* with other processes. This class maintains a single file lock per file, since multiple concurrent {@link FileLock}s on the same
* file are not allowed by the JVM. It also uses reference counts to remove locks only when a named lock is no longer being used.
*/
public final class FileLocks {
private static final FileLocks INSTANCE = new FileLocks();
/**
* Obtain the singleton instance for this virtual machine.
*
* @return the file lock manager; never null
*/
public static FileLocks get() {
return INSTANCE;
}
private final Lock masterLock = new ReentrantLock();
private final Map locks = new HashMap();
private FileLocks() {
// Prevent others from instantiating
}
/**
* Obtain a write lock for the supplied file. When this method returns, the current thread will have obtained the lock.
* Therefore, there is no need to call any of the lock methods (e.g., {@link Lock#lock()}, {@link Lock#lockInterruptibly()},
* {@link Lock#tryLock()} or {@link Lock#tryLock(long, TimeUnit)}), as those methods will immediately return.
*
* @param file the file to be locked; may not be null
* @return the lock held by the current thread; never null
* @throws IOException if there is a problem obtaining the file lock
*/
public WrappedLock writeLock( File file ) throws IOException {
return lock(file, true, true);
}
/**
* Obtain a read lock for the supplied file. When this method returns, the current thread will have obtained the lock.
* Therefore, there is no need to call any of the lock methods (e.g., {@link Lock#lock()}, {@link Lock#lockInterruptibly()},
* {@link Lock#tryLock()} or {@link Lock#tryLock(long, TimeUnit)}), as those methods will immediately return.
*
* @param file the file to be locked; may not be null
* @return the lock held by the current thread; never null
* @throws IOException if there is a problem obtaining the file lock
*/
public WrappedLock readLock( File file ) throws IOException {
return lock(file, false, true);
}
/**
* Try to obtain a write lock for the supplied file. When this method returns a non-null lock, the current thread will have
* obtained the lock. Therefore, there is no need to call any of the lock methods (e.g., {@link Lock#lock()},
* {@link Lock#lockInterruptibly()}, {@link Lock#tryLock()} or {@link Lock#tryLock(long, TimeUnit)}), as those methods will
* immediately return.
*
* However, if this method returns null
, this method could not obtain the lock.
*
*
* @param file the file to be locked; may not be null
* @return the lock held by the current thread; null if the lock could not be obtained
* @throws IOException if there is a problem obtaining the file lock
*/
public WrappedLock tryWriteLock( File file ) throws IOException {
return lock(file, true, false);
}
/**
* Try to obtain a read lock for the supplied file. When this method returns a non-null lock, the current thread will have
* obtained the lock. Therefore, there is no need to call any of the lock methods (e.g., {@link Lock#lock()},
* {@link Lock#lockInterruptibly()}, {@link Lock#tryLock()} or {@link Lock#tryLock(long, TimeUnit)}), as those methods will
* immediately return.
*
* However, if this method returns null
, this method could not obtain the lock.
*
*
* @param file the file to be locked; may not be null
* @return the lock held by the current thread; null if the lock could not be obtained
* @throws IOException if there is a problem obtaining the file lock
*/
public WrappedLock tryReadLock( File file ) throws IOException {
return lock(file, false, false);
}
protected final WrappedLock lock( File file,
boolean writeLock,
boolean block ) throws IOException {
LockHolder holder = null;
try {
masterLock.lock();
// Look for a lock with the supplied name ...
holder = locks.get(file.getAbsolutePath());
if (holder == null) {
// Create a new named lock, which wraps and obtains the actual lock ...
holder = new LockHolder(file);
// Now store the wrapper in the map ...
locks.put(file.getAbsolutePath(), holder);
// Obtain and return the read or write lock (which we just created and nobody else can even see yet) ...
return holder.lock(writeLock, block);
}
// Otherwise we found the lock and just need to increment the counter within the 'masterLock' scope ...
holder.incrementReferenceCount();
} finally {
masterLock.unlock();
}
// Now be sure to obtain the lock (outside of the 'masterLock' scope) ...
return holder.lock(writeLock, block);
}
protected void unlock( LockHolder holder,
Lock rawLock ) {
try {
masterLock.lock();
try {
// Decrement the counter ...
if (holder.decrementReferenceCount() == 0) {
// This was the last lock holder, so remove it from the map ...
locks.remove(holder.file.getAbsolutePath());
}
} finally {
// And always unlock the 'raw' (not wrapped) lock ...
rawLock.unlock();
}
} finally {
masterLock.unlock();
}
}
/**
* Get the number of named locks.
*
* @return the number of named locks; never negative
*/
public int size() {
try {
masterLock.lock();
return locks.size();
} finally {
masterLock.unlock();
}
}
protected static class WrappedLock implements Lock {
protected final LockHolder holder;
protected final Lock actualLock;
protected WrappedLock( LockHolder namedLock,
Lock lock ) {
this.holder = namedLock;
this.actualLock = lock;
}
@Override
public void lock() {
// no need to do anything, as our lock is already held by this thread
}
@Override
public void lockInterruptibly() {
// no need to do anything, as our lock is already held by this thread
}
@Override
public Condition newCondition() {
// Returning the actual lock's condition shouldn't really affect our reference count, since the condition
// can only be used when the lock is held
return actualLock.newCondition();
}
@Override
public boolean tryLock() {
// no need to do anything, as our lock is already held by this thread
return true;
}
@Override
public boolean tryLock( long time,
TimeUnit unit ) {
// no need to do anything, as our lock is already held by this thread
return true;
}
@Override
public void unlock() {
holder.unlock(this.actualLock);
}
/**
* Returns the {@link java.nio.channels.FileLock#channel()} associated with the file lock.
*
* @return a {@code FileChannel} instance.
*/
public FileChannel lockedFileChannel() {
return holder.lockedFileChannel();
}
}
protected final class LockHolder {
protected final File file;
protected final WrappedLock readLock;
protected final WrappedLock writeLock;
private final ReadWriteLock lock = new ReentrantReadWriteLock();
private final AtomicInteger referenceCount = new AtomicInteger(1);
private final Lock fileLockLock = new ReentrantLock();
@GuardedBy( "fileLockLock" )
private FileLock fileLock;
@GuardedBy( "fileLockLock" )
private int lockedReaders = 0;
protected LockHolder( File file ) {
this.file = file;
this.readLock = new WrappedLock(this, lock.readLock());
this.writeLock = new WrappedLock(this, lock.writeLock());
}
protected FileChannel lockedFileChannel() {
try {
fileLockLock.lock();
return fileLock != null ? fileLock.channel() : null;
} finally {
fileLockLock.unlock();
}
}
protected void incrementReferenceCount() {
referenceCount.incrementAndGet();
}
protected int decrementReferenceCount() {
return referenceCount.decrementAndGet();
}
protected WrappedLock lock( boolean write,
boolean block ) throws IOException {
if (write) {
this.lock.writeLock().lock();
assert this.fileLock == null;
assert this.lockedReaders == 0;
// We have an exclusive write lock in this VM, so we can safely create the file lock ...
RandomAccessFile raf = new RandomAccessFile(file, "rw");
FileChannel channel = raf.getChannel();
// Create a exclusive (non-shared) lock that does not allow other readers ...
if (block) {
fileLock = channel.lock(0, Long.MAX_VALUE, false);
} else {
fileLock = channel.tryLock(0, Long.MAX_VALUE, false);
if (fileLock == null) {
// Couldn't immediately get our write-lock, so unlock the read lock and return ...
this.lock.writeLock().unlock();
return null;
}
}
// And return the wrapped write lock that will forward the unlock to us ...
return writeLock;
}
// This is a read lock, so grab one of the read locks ...
this.lock.readLock().lock();
// We need to create a shared file lock for reading, but we only want to do this once for the first read lock ...
try {
fileLockLock.lock();
if (fileLock == null) {
assert this.lockedReaders == 0;
// We have an exclusive write lock in this VM, so we can safely create the file lock ...
RandomAccessFile raf = new RandomAccessFile(file, "r");
FileChannel channel = raf.getChannel();
// Create a shared lock that allows other readers ...
if (block) {
fileLock = channel.lock(0, Long.MAX_VALUE, true);
} else {
fileLock = channel.tryLock(0, Long.MAX_VALUE, true);
if (fileLock == null) {
// Couldn't immediately get our read-lock, so unlock the read lock and return ...
this.lock.readLock().unlock();
return null;
}
}
} else {
assert this.lockedReaders != 0;
}
lockedReaders++;
} finally {
fileLockLock.unlock();
}
return readLock;
}
protected void unlock( Lock lock ) {
boolean readLock = lock == this.lock.readLock();
try {
fileLockLock.lock();
boolean unlock = true;
if (readLock) {
--lockedReaders;
if (lockedReaders > 0) unlock = false;
}
// First get rid of the file lock ...
if (unlock && fileLock != null && fileLock.channel().isOpen()) {
try {
if (fileLock.channel().isOpen()) {
fileLock.channel().close();
} else {
fileLock.release();
}
} catch (IOException e) {
throw new RuntimeException(e);
} finally {
fileLock = null;
}
}
} finally {
try {
fileLockLock.unlock();
} finally {
FileLocks.this.unlock(this, lock);
}
}
}
@Override
public String toString() {
return "FileLockHolder \"" + file + "\" (" + referenceCount.get() + " referrers)";
}
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy