
alluxio.master.file.meta.InodeLockManager Maven / Gradle / Ivy
/*
* The Alluxio Open Foundation licenses this work under the Apache License, version 2.0
* (the "License"). You may not use this work except in compliance with the License, which is
* available at www.apache.org/licenses/LICENSE-2.0
*
* This software is distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
* either express or implied, as more fully set forth in the License.
*
* See the NOTICE file distributed with this work for information regarding copyright ownership.
*/
package alluxio.master.file.meta;
import alluxio.collections.LockPool;
import alluxio.concurrent.LockMode;
import alluxio.conf.PropertyKey;
import alluxio.conf.ServerConfiguration;
import alluxio.resource.LockResource;
import alluxio.util.interfaces.Scoped;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.google.common.util.concurrent.Striped;
import java.util.Map.Entry;
import java.util.Optional;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
/**
* Class for managing inode locking. We manage locks centrally instead of embedded in the inode
* tree. This allows us to create locks only as needed, and garbage collect locks that aren't in
* use. As a result, we save memory when the inode tree contains many millions of files.
*
* We use WeakSafeReentrantReadWriteLock instead of ReentrantReadWriteLock because the read locks
* and write locks returned by ReentrantReadWriteLock do not contain a reference to the original
* ReentrantReadWriteLock, so the original lock can be garbage collected early.
* WeakSafeReentrantReadWriteLock stores the reference to the original lock to avoid this problem.
* See https://github.com/google/guava/issues/2477
*/
public class InodeLockManager {
/**
* Pool for supplying inode locks. To lock an inode, its inode id must be searched in this
* pool to get the appropriate read lock.
*
* We use weak values so that when nothing holds a reference to
* a lock, the garbage collector can remove the lock's entry from the pool.
*/
public final LockPool mInodeLocks =
new LockPool<>((key)-> new ReentrantReadWriteLock(),
ServerConfiguration.getInt(PropertyKey.MASTER_LOCK_POOL_INITSIZE),
ServerConfiguration.getInt(PropertyKey.MASTER_LOCK_POOL_LOW_WATERMARK),
ServerConfiguration.getInt(PropertyKey.MASTER_LOCK_POOL_HIGH_WATERMARK),
ServerConfiguration.getInt(PropertyKey.MASTER_LOCK_POOL_CONCURRENCY_LEVEL));
/**
* Cache for supplying edge locks, similar to mInodeLocks.
*/
public final LockPool mEdgeLocks =
new LockPool<>((key)-> new ReentrantReadWriteLock(),
ServerConfiguration.getInt(PropertyKey.MASTER_LOCK_POOL_INITSIZE),
ServerConfiguration.getInt(PropertyKey.MASTER_LOCK_POOL_LOW_WATERMARK),
ServerConfiguration.getInt(PropertyKey.MASTER_LOCK_POOL_HIGH_WATERMARK),
ServerConfiguration.getInt(PropertyKey.MASTER_LOCK_POOL_CONCURRENCY_LEVEL));
/**
* Locks for guarding changes to last modified time and size on read-locked parent inodes.
*
* When renaming, creating, or deleting, we update the last modified time and size of the parent
* inode while holding only a read lock. In the presence of concurrent operations, this could
* cause the last modified time to decrease, or lead to incorrect directory sizes. To avoid this,
* we guard the parent inode read-modify-write with this lock. To avoid deadlock, a thread should
* never acquire more than one of these locks at the same time, and no other locks should be taken
* while holding one of these locks.
*/
private final Striped mParentUpdateLocks = Striped.lock(1_000);
/**
* Cache for supplying inode persistence locks. Before a thread can persist an inode, it must
* acquire the persisting lock for the inode. The cache maps inode ids to AtomicBooleans used to
* provide mutual exclusion for inode persisting threads.
*/
private final LoadingCache mPersistingLocks =
CacheBuilder.newBuilder()
.weakValues()
.initialCapacity(1_000)
.concurrencyLevel(100)
.build(new CacheLoader() {
@Override
public AtomicBoolean load(Long key) {
return new AtomicBoolean();
}
});
@VisibleForTesting
boolean inodeReadLockedByCurrentThread(long inodeId) {
return mInodeLocks.getRawReadWriteLock(inodeId).getReadHoldCount() > 0;
}
@VisibleForTesting
boolean inodeWriteLockedByCurrentThread(long inodeId) {
return mInodeLocks.getRawReadWriteLock(inodeId).getWriteHoldCount() > 0;
}
@VisibleForTesting
boolean edgeReadLockedByCurrentThread(Edge edge) {
return mEdgeLocks.getRawReadWriteLock(edge).getReadHoldCount() > 0;
}
@VisibleForTesting
boolean edgeWriteLockedByCurrentThread(Edge edge) {
return mEdgeLocks.getRawReadWriteLock(edge).getWriteHoldCount() > 0;
}
/**
* Asserts that all locks have been released, throwing an exception if any locks are still taken.
*/
@VisibleForTesting
public void assertAllLocksReleased() {
assertAllLocksReleased(mEdgeLocks);
assertAllLocksReleased(mInodeLocks);
}
private void assertAllLocksReleased(LockPool pool) {
for (Entry entry : pool.getEntryMap().entrySet()) {
ReentrantReadWriteLock lock = entry.getValue();
if (lock.isWriteLocked()) {
throw new RuntimeException(
String.format("Found a write-locked lock for %s", entry.getKey()));
}
if (lock.getReadLockCount() > 0) {
throw new RuntimeException(
String.format("Found a read-locked lock for %s", entry.getKey()));
}
}
}
/**
* Acquires an inode lock.
*
* @param inode the inode to lock
* @param mode the mode to lock in
* @return a lock resource which must be closed to release the lock
*/
public LockResource lockInode(InodeView inode, LockMode mode) {
return mInodeLocks.get(inode.getId(), mode);
}
/**
* Attempts to acquire an inode lock.
*
* @param inodeId the inode id to try locking
* @param mode the mode to lock in
* @return either an empty optional, or a lock resource which must be closed to release the lock
*/
public Optional tryLockInode(Long inodeId, LockMode mode) {
return mInodeLocks.tryGet(inodeId, mode);
}
/**
* Acquires an edge lock.
*
* @param edge the edge to lock
* @param mode the mode to lock in
* @return a lock resource which must be closed to release the lock
*/
public LockResource lockEdge(Edge edge, LockMode mode) {
return mEdgeLocks.get(edge, mode);
}
/**
* Attempts to acquire an edge lock.
*
* @param edge the edge to try locking
* @param mode the mode to lock in
* @return either an empty optional, or a lock resource which must be closed to release the lock
*/
public Optional tryLockEdge(Edge edge, LockMode mode) {
return mEdgeLocks.tryGet(edge, mode);
}
/**
* Tries to acquire a lock for persisting the specified inode id.
*
* @param inodeId the inode to acquire the lock for
* @return an optional wrapping a closure for releasing the lock on success, or Optional.empty if
* the lock is already taken
*/
public Optional tryAcquirePersistingLock(long inodeId) {
AtomicBoolean lock = mPersistingLocks.getUnchecked(inodeId);
if (lock.compareAndSet(false, true)) {
return Optional.of(() -> lock.set(false));
}
return Optional.empty();
}
/**
* Acquires the lock for modifying an inode's last modified time or size. As a pre-requisite, the
* current thread should already hold a read lock on the inode.
*
* @param inodeId the id of the inode to lock
* @return a lock resource which must be closed to release the lock
*/
public LockResource lockUpdate(long inodeId) {
return new LockResource(mParentUpdateLocks.get(inodeId));
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy