/*
* 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.exception.InvalidPathException;
import java.util.ArrayList;
import java.util.List;
import javax.annotation.concurrent.ThreadSafe;
/**
* Manages the locks for a list of {@link Inode}.
*/
@ThreadSafe
public final class InodeLockList implements AutoCloseable {
private final List<Inode<?>> mInodes;
private final List<InodeTree.LockMode> mLockModes;
InodeLockList() {
mInodes = new ArrayList<>();
mLockModes = new ArrayList<>();
}
/**
* Locks the given inode in read mode, and adds it to this lock list. This call should only be
* used when locking the root or an inode by id and not path or parent.
*
* @param inode the inode to lock
*/
public synchronized void lockRead(Inode<?> inode) {
inode.lockRead();
mInodes.add(inode);
mLockModes.add(InodeTree.LockMode.READ);
}
/**
* Locks the given inode in read mode, and adds it to this lock list. This method ensures the
* parent is the expected parent inode.
*
* NOTE: This method assumes that the inode path to the parent has been read locked.
*
* @param inode the inode to lock
* @param parent the expected parent inode
* @throws InvalidPathException if the inode is no long consistent with the caller's expectations
*/
public synchronized void lockReadAndCheckParent(Inode<?> inode, Inode parent)
throws InvalidPathException {
inode.lockReadAndCheckParent(parent);
mInodes.add(inode);
mLockModes.add(InodeTree.LockMode.READ);
}
/**
* Locks the given inode in read mode, and adds it to this lock list. This method ensures the
* parent is the expected parent inode, and the name of the inode is the expected name.
*
* NOTE: This method assumes that the inode path to the parent has been read locked.
*
* @param inode the inode to lock
* @param parent the expected parent inode
* @param name the expected name of the inode to be locked
* @throws InvalidPathException if the inode is not consistent with the caller's expectations
*/
public synchronized void lockReadAndCheckNameAndParent(Inode<?> inode, Inode parent, String name)
throws InvalidPathException {
inode.lockReadAndCheckNameAndParent(parent, name);
mInodes.add(inode);
mLockModes.add(InodeTree.LockMode.READ);
}
/**
* Unlocks the last inode that was locked.
*/
public synchronized void unlockLast() {
if (mInodes.isEmpty()) {
return;
}
Inode<?> inode = mInodes.remove(mInodes.size() - 1);
InodeTree.LockMode lockMode = mLockModes.remove(mLockModes.size() - 1);
if (lockMode == InodeTree.LockMode.READ) {
inode.unlockRead();
} else {
inode.unlockWrite();
}
}
/**
* Downgrades the last inode that was locked, if the inode was previously WRITE locked. If the
* inode was previously READ locked, no additional locking will occur.
*/
public synchronized void downgradeLast() {
if (mInodes.isEmpty()) {
return;
}
if (mLockModes.get(mLockModes.size() - 1) != InodeTree.LockMode.READ) {
// The last inode was previously WRITE locked, so downgrade the lock.
Inode<?> inode = mInodes.get(mInodes.size() - 1);
inode.lockRead();
inode.unlockWrite();
// Update the last lock mode to READ
mLockModes.remove(mLockModes.size() - 1);
mLockModes.add(InodeTree.LockMode.READ);
}
}
/**
* Locks the given inode in write mode, and adds it to this lock list. This call should only be
* used when locking the root or an inode by id and not path or parent.
*
* @param inode the inode to lock
*/
public synchronized void lockWrite(Inode<?> inode) {
inode.lockWrite();
mInodes.add(inode);
mLockModes.add(InodeTree.LockMode.WRITE);
}
/**
* Locks the given inode in write mode, and adds it to this lock list. This method ensures the
* parent is the expected parent inode.
*
* NOTE: This method assumes that the inode path to the parent has been read locked.
*
* @param inode the inode to lock
* @param parent the expected parent inode
* @throws InvalidPathException if the inode is not consistent with the caller's expectations
*/
public synchronized void lockWriteAndCheckParent(Inode<?> inode, Inode parent)
throws InvalidPathException {
inode.lockWriteAndCheckParent(parent);
mInodes.add(inode);
mLockModes.add(InodeTree.LockMode.WRITE);
}
/**
* Locks the given inode in write mode, and adds it to this lock list. This method ensures the
* parent is the expected parent inode, and the name of the inode is the expected name.
*
* NOTE: This method assumes that the inode path to the parent has been read locked.
*
* @param inode the inode to lock
* @param parent the expected parent inode
* @param name the expected name of the inode to be locked
* @throws InvalidPathException if the inode is not consistent with the caller's expectations
*/
public synchronized void lockWriteAndCheckNameAndParent(Inode<?> inode, Inode parent, String name)
throws InvalidPathException {
inode.lockWriteAndCheckNameAndParent(parent, name);
mInodes.add(inode);
mLockModes.add(InodeTree.LockMode.WRITE);
}
/**
* @return the list of inodes locked in this lock list, in order of when the inodes were locked
*/
public synchronized List<Inode<?>> getInodes() {
return mInodes;
}
@Override
public synchronized void close() {
for (int i = mInodes.size() - 1; i >= 0; i--) {
Inode<?> inode = mInodes.get(i);
InodeTree.LockMode lockMode = mLockModes.get(i);
if (lockMode == InodeTree.LockMode.READ) {
inode.unlockRead();
} else {
inode.unlockWrite();
}
}
mInodes.clear();
mLockModes.clear();
}
}