All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.modeshape.jcr.value.binary.FileLocks Maven / Gradle / Ivy

There is a newer version: 5.4.1.Final
Show newest version
/*
 * 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