* Refactored node locking methods. There's now a LockIterator class which helps

with locking.
* Implemented methods for locking two directories. It's a bit complex, since
  we need to enforce a global locking order on the node tree to avoid
  deadlocks. Untested yet.


git-svn-id: file:///srv/svn/repos/haiku/haiku/trunk@29738 a95241bf-73f2-0310-859d-f6bbb57e9c96
This commit is contained in:
Ingo Weinhold
2009-03-26 22:41:00 +00:00
parent d63a77f913
commit 85ea1e09d3
2 changed files with 434 additions and 50 deletions
@@ -19,6 +19,10 @@
#include "../kernel_emu.h"
// The maximal node tree hierarchy levels we support.
static const uint32 kMaxNodeTreeDepth = 1024;
struct FUSEVolume::DirEntryCache {
DirEntryCache()
:
@@ -298,6 +302,110 @@ struct FUSEVolume::ReadDirBuffer {
};
struct FUSEVolume::LockIterator {
FUSEVolume* volume;
FUSENode* firstNode;
FUSENode* lastLockedNode;
FUSENode* nextNode;
FUSENode* stopBeforeNode;
bool writeLock;
LockIterator(FUSEVolume* volume, FUSENode* node, bool writeLock,
FUSENode* stopBeforeNode)
:
volume(volume),
firstNode(node),
lastLockedNode(NULL),
nextNode(node),
stopBeforeNode(stopBeforeNode),
writeLock(writeLock)
{
}
~LockIterator()
{
Unlock();
}
void SetTo(FUSEVolume* volume, FUSENode* node, bool writeLock,
FUSENode* stopBeforeNode)
{
Unlock();
this->volume = volume;
this->firstNode = node;
this->lastLockedNode = NULL;
this->nextNode = node;
this->stopBeforeNode = stopBeforeNode;
this->writeLock = writeLock;
}
status_t LockNext(bool* _done, bool* _volumeUnlocked)
{
// increment the ref count first
nextNode->refCount++;
if (volume->fLockManager.TryGenericLock(
nextNode == firstNode && writeLock, nextNode)) {
// got the lock
*_volumeUnlocked = false;
} else {
// node is locked -- we need to unlock the volume and wait for
// the lock
volume->fLock.Unlock();
status_t error = volume->fLockManager.GenericLock(
nextNode == firstNode && writeLock, nextNode);
volume->fLock.Lock();
*_volumeUnlocked = false;
if (error != B_OK) {
volume->_PutNode(nextNode);
return error;
}
}
lastLockedNode = nextNode;
// get the parent node
FUSENode* parent = nextNode->Parent();
if (parent == stopBeforeNode || parent == nextNode) {
if (parent == nextNode)
parent = NULL;
*_done = true;
} else
*_done = false;
nextNode = parent;
return B_OK;
}
void Unlock()
{
if (lastLockedNode == NULL)
return;
volume->_UnlockNodeChainInternal(firstNode, writeLock, lastLockedNode,
NULL);
lastLockedNode = NULL;
nextNode = firstNode;
}
void SetStopBeforeNode(FUSENode* stopBeforeNode)
{
this->stopBeforeNode = stopBeforeNode;
}
void Detach()
{
lastLockedNode = NULL;
nextNode = firstNode;
}
};
struct FUSEVolume::RWLockableReadLocking {
RWLockableReadLocking(FUSEVolume* volume)
:
@@ -1891,7 +1999,7 @@ FUSEVolume::_RemoveEntry(FUSENode* dir, const char* name)
node is write-locked, if \a writeLock is \c true, read-locked otherwise. All
ancestors are always read-locked in either case.
If \a parent is \c true, the given node itself is ignored, but locking
If \a lockParent is \c true, the given node itself is ignored, but locking
starts with the parent node of the given node (\a writeLock applies to the
parent node then).
@@ -1900,67 +2008,42 @@ FUSEVolume::_RemoveEntry(FUSENode* dir, const char* name)
The volume lock must not be held.
*/
status_t
FUSEVolume::_LockNodeChain(FUSENode* node, bool parent, bool writeLock)
FUSEVolume::_LockNodeChain(FUSENode* node, bool lockParent, bool writeLock)
{
AutoLocker<Locker> locker(fLock);
if (parent && node != NULL)
FUSENode* originalNode = node;
if (lockParent && node != NULL)
node = node->Parent();
if (node == NULL)
RETURN_ERROR(B_ENTRY_NOT_FOUND);
FUSENode* originalNode = node;
status_t error = B_OK;
LockIterator iterator(this, node, writeLock, NULL);
bool done;
do {
// increment the ref count first
node->refCount++;
bool volumeUnlocked;
status_t error = iterator.LockNext(&done, &volumeUnlocked);
if (error != B_OK)
RETURN_ERROR(error);
// lock the node
if (!fLockManager.TryGenericLock(node == originalNode && writeLock,
node)) {
// node is locked -- we need to unlock the volume and wait for
// the lock
locker.Unlock();
error = fLockManager.GenericLock(node == originalNode && writeLock,
node);
locker.Lock();
if (error != B_OK)
break;
if (volumeUnlocked) {
// check whether we're still locking the right node
if (lockParent && originalNode->Parent() != node) {
// We don't -- unlock everything and try again.
node = originalNode->Parent();
iterator.SetTo(this, node, writeLock, NULL);
}
}
// get the parent node
FUSENode* parent = node->Parent();
if (parent == node)
break;
node = parent;
} while (node != NULL);
} while (!done);
// Fail, if we couldn't lock all nodes up to the root.
if (error == B_OK && node == NULL)
error = B_ENTRY_NOT_FOUND;
if (error != B_OK) {
// locking failed -- unlock and release the references of all
// nodes
FUSENode* stopNode = node;
node = originalNode;
while (node != stopNode) {
fLockManager.GenericUnlock(
node == originalNode && writeLock, node);
FUSENode* parent = node->Parent();
_PutNode(node);
node = parent;
}
if (stopNode != NULL)
_PutNode(stopNode);
RETURN_ERROR(error);
}
if (iterator.lastLockedNode != fRootNode)
RETURN_ERROR(B_ENTRY_NOT_FOUND);
iterator.Detach();
return B_OK;
}
@@ -1973,21 +2056,299 @@ FUSEVolume::_UnlockNodeChain(FUSENode* node, bool parent, bool writeLock)
if (parent && node != NULL)
node = node->Parent();
_UnlockNodeChainInternal(node, writeLock, NULL, NULL);
}
/*! Unlocks all nodes from \a node up to (and including) \a stopNode (if
\c NULL, it is ignored). If \a stopBeforeNode is given, the method stops
before unlocking that node.
The volume lock must be held.
*/
void
FUSEVolume::_UnlockNodeChainInternal(FUSENode* node, bool writeLock,
FUSENode* stopNode, FUSENode* stopBeforeNode)
{
FUSENode* originalNode = node;
while (node != NULL) {
while (node != NULL && stopBeforeNode != stopBeforeNode) {
FUSENode* parent = node->Parent();
fLockManager.GenericUnlock(node == originalNode && writeLock, node);
_PutNode(node);
if (parent == node)
if (node == stopNode || parent == node)
break;
node = parent;
}
}
status_t
FUSEVolume::_LockNodeChains(FUSENode* node1, bool lockParent1, bool writeLock1,
FUSENode* node2, bool lockParent2, bool writeLock2)
{
// Since in this case locking is more complicated, we use a helper method.
// It does the main work, but simply returns telling us to retry when the
// node hierarchy changes.
bool retry;
do {
status_t error = _LockNodeChainsInternal(node1, lockParent1, writeLock1,
node2, lockParent2, writeLock2, &retry);
if (error != B_OK)
return error;
} while (retry);
return B_OK;
}
status_t
FUSEVolume::_LockNodeChainsInternal(FUSENode* node1, bool lockParent1,
bool writeLock1, FUSENode* node2, bool lockParent2, bool writeLock2,
bool* _retry)
{
// Locking order:
// * A child of a node has to be locked before its parent.
// * Sibling nodes have to be locked in ascending node ID order.
//
// This implies the following locking algorithm:
// * We find the closest common ancestor of the two given nodes (might even
// be one of the given nodes).
// * We lock all ancestors on one branch (the one with the lower common
// ancestor child node ID), but not including the common ancestor.
// * We lock all ancestors on the other branch, not including the common
// ancestor.
// * We lock the common ancestor and all of its ancestors up to the root
// node.
//
// When the hierarchy changes while we're waiting for a lock, we recheck the
// conditions and in doubt have to be restarted.
AutoLocker<Locker> locker(fLock);
FUSENode* originalNode1 = node1;
FUSENode* originalNode2 = node2;
if (lockParent1 && node1 != NULL)
node1 = node1->Parent();
if (lockParent2 && node2 != NULL)
node2 = node2->Parent();
if (node1 == NULL || node2 == NULL)
RETURN_ERROR(B_ENTRY_NOT_FOUND);
// find the first common ancestor
FUSENode* commonAncestor;
bool inverseLockingOrder;
if (!_FindCommonAncestor(node1, node2, &commonAncestor,
&inverseLockingOrder)) {
RETURN_ERROR(B_ENTRY_NOT_FOUND);
}
// lock the both node chains up to (but not including) the common ancestor
LockIterator iterator1(this, node1, writeLock1, commonAncestor);
LockIterator iterator2(this, node2, writeLock2, commonAncestor);
for (int i = 0; i < 2; i++) {
LockIterator& iterator = (i == 0) != inverseLockingOrder
? iterator1 : iterator2;
// If the node is the common ancestor, don't enter the "do" loop, since
// we don't have to lock anything here.
if (iterator.firstNode == commonAncestor)
continue;
bool done;
do {
bool volumeUnlocked;
status_t error = iterator1.LockNext(&done, &volumeUnlocked);
if (error != B_OK)
RETURN_ERROR(error);
if (volumeUnlocked) {
// check whether we're still locking the right nodes
if (lockParent1 && originalNode1->Parent() != node1
|| lockParent2 && originalNode2->Parent() != node2) {
// We don't -- unlock everything and retry.
*_retry = true;
return B_OK;
}
// also recheck the common ancestor
FUSENode* newCommonParent;
bool newInverseLockingOrder;
if (!_FindCommonAncestor(node1, node2, &newCommonParent,
&newInverseLockingOrder)) {
RETURN_ERROR(B_ENTRY_NOT_FOUND);
}
if (newCommonParent != commonAncestor
|| inverseLockingOrder != newInverseLockingOrder) {
// Something changed -- unlock everything and retry.
*_retry = true;
return B_OK;
}
}
} while (!done);
}
// Continue locking from the common ancestor to the root. If one of the
// given nodes is the common ancestor and shall be write locked, we need to
// use the respective iterator.
LockIterator& iterator = node2 == commonAncestor && writeLock2
? iterator2 : iterator1;
iterator.SetStopBeforeNode(NULL);
bool done;
do {
bool volumeUnlocked;
status_t error = iterator.LockNext(&done, &volumeUnlocked);
if (error != B_OK)
RETURN_ERROR(error);
if (volumeUnlocked) {
// check whether we're still locking the right nodes
if (lockParent1 && originalNode1->Parent() != node1
|| lockParent2 && originalNode2->Parent() != node2) {
// We don't -- unlock everything and retry.
*_retry = true;
return B_OK;
}
// Also recheck the common ancestor, if we have just locked it.
// Otherwise we can just continue to lock, since nothing below the
// previously locked node can have changed.
if (iterator1.lastLockedNode == commonAncestor) {
FUSENode* newCommonParent;
bool newInverseLockingOrder;
if (!_FindCommonAncestor(node1, node2, &newCommonParent,
&newInverseLockingOrder)) {
RETURN_ERROR(B_ENTRY_NOT_FOUND);
}
if (newCommonParent != commonAncestor
|| inverseLockingOrder != newInverseLockingOrder) {
// Something changed -- unlock everything and retry.
*_retry = true;
return B_OK;
}
}
}
} while (!done);
// Fail, if we couldn't lock all nodes up to the root.
if (iterator1.lastLockedNode != fRootNode)
RETURN_ERROR(B_ENTRY_NOT_FOUND);
// everything went fine
iterator1.Detach();
iterator2.Detach();
*_retry = false;
return B_OK;
}
void
FUSEVolume::_UnlockNodeChains(FUSENode* node1, bool lockParent1,
bool writeLock1, FUSENode* node2, bool lockParent2, bool writeLock2)
{
AutoLocker<Locker> locker(fLock);
if (lockParent1 && node1 != NULL)
node1 = node1->Parent();
if (lockParent2 && node2 != NULL)
node2 = node2->Parent();
if (node1 == NULL || node2 == NULL)
return;
// find the common ancestor
FUSENode* commonAncestor;
bool inverseLockingOrder;
if (!_FindCommonAncestor(node1, node2, &commonAncestor,
&inverseLockingOrder)) {
return;
}
// Unlock one branch up to the common ancestor and then the complete other
// branch up to the root. If one of the given nodes is the common ancestor,
// we need to make sure, we write-unlock it, if requested.
if (node2 == commonAncestor && writeLock2) {
_UnlockNodeChainInternal(node1, writeLock1, NULL, commonAncestor);
_UnlockNodeChainInternal(node2, writeLock2, NULL, NULL);
} else {
_UnlockNodeChainInternal(node2, writeLock2, NULL, commonAncestor);
_UnlockNodeChainInternal(node1, writeLock1, NULL, NULL);
}
}
bool
FUSEVolume::_FindCommonAncestor(FUSENode* node1, FUSENode* node2,
FUSENode** _commonAncestor, bool* _inverseLockingOrder)
{
// handle trivial special case -- both nodes are the same
if (node1 == node2) {
*_commonAncestor = node1;
*_inverseLockingOrder = false;
return true;
}
// get the ancestors of both nodes
FUSENode* ancestors1[kMaxNodeTreeDepth];
FUSENode* ancestors2[kMaxNodeTreeDepth];
uint32 count1;
uint32 count2;
if (!_GetNodeAncestors(node1, ancestors1, &count1)
|| !_GetNodeAncestors(node2, ancestors2, &count2)) {
return false;
}
// find the first ancestor not common to both nodes
uint32 index = 0;
for (; index < count1 && index < count2; index++) {
if (ancestors1[index] != ancestors2[index]) {
*_commonAncestor = ancestors1[index - 1];
*_inverseLockingOrder
= ancestors1[index]->id > ancestors2[index]->id;
return true;
}
}
// one node is an ancestor of the other
*_commonAncestor = ancestors1[index - 1];
*_inverseLockingOrder = index == count1;
return true;
}
bool
FUSEVolume::_GetNodeAncestors(FUSENode* node, FUSENode** ancestors,
uint32* _count)
{
uint32 count = 0;
while (node != NULL && count < kMaxNodeTreeDepth) {
ancestors[count++] = node;
if (node == fRootNode) {
*_count = count;
return true;
}
node = node->Parent();
}
// Either the node is not in the tree or we hit the array limit.
return false;
}
status_t
FUSEVolume::_BuildPath(FUSENode* dir, const char* entryName, char* path,
size_t& pathLen)
@@ -116,6 +116,7 @@ private:
struct FileCookie;
struct AttrDirCookie;
struct ReadDirBuffer;
struct LockIterator;
struct RWLockableReadLocking;
struct RWLockableWriteLocking;
struct RWLockableReadLocker;
@@ -124,6 +125,7 @@ private:
struct NodeReadLocker;
struct NodeWriteLocker;
friend struct LockIterator;
friend struct RWLockableReadLocking;
friend struct RWLockableWriteLocking;
friend struct NodeLocker;
@@ -144,10 +146,31 @@ private:
void _RemoveEntry(FUSEEntry* entry);
status_t _RemoveEntry(FUSENode* dir, const char* name);
status_t _LockNodeChain(FUSENode* node, bool parent,
status_t _LockNodeChain(FUSENode* node, bool lockParent,
bool writeLock);
void _UnlockNodeChain(FUSENode* node, bool parent,
bool writeLock);
void _UnlockNodeChainInternal(FUSENode* node,
bool writeLock, FUSENode* stopNode,
FUSENode* stopBeforeNode);
status_t _LockNodeChains(FUSENode* node1,
bool lockParent1, bool writeLock1,
FUSENode* node2, bool lockParent2,
bool writeLock2);
status_t _LockNodeChainsInternal(FUSENode* node1,
bool lockParent1, bool writeLock1,
FUSENode* node2, bool lockParent2,
bool writeLock2, bool* _retry);
void _UnlockNodeChains(FUSENode* node1, bool parent1,
bool writeLock1, FUSENode* node2,
bool parent2, bool writeLock2);
bool _FindCommonAncestor(FUSENode* node1,
FUSENode* node2, FUSENode** _commonAncestor,
bool* _inverseLockingOrder);
bool _GetNodeAncestors(FUSENode* node,
FUSENode** ancestors, uint32* _count);
status_t _BuildPath(FUSENode* dir, const char* entryName,
char* path, size_t& pathLen);