Advisory locking fixes:

* Made the access strategy to vnode::advisory_locking consistent.
  get_advisory_locking() was guarding it with sVnodeMutex,
  create_advisory_locking() was using atomic_pointer_test_and_set(), and
  release_advisory_lock() just set it unguardedly. We do use sVnodeMutex
  consequently, now.
* Beautified create_advisory_locking() (got rid of the gotos,
  reorganized the control flow).
* Fixed race conditions in acquire_advisory_lock(). It was always
  unlocking and relocking the advisory_locking object when it didn't
  have to wait, but in the meantime someone else could have changed the
  locking situation. Reorganized the control flow, so that it only drops
  the lock when it has to fail or wait. Using create_advisory_locking()
  upfront simplifies the code quite a bit (and fixes another race
  condition).

APR's testprocmutex test seems happy now, at least.


git-svn-id: file:///srv/svn/repos/haiku/haiku/trunk@25407 a95241bf-73f2-0310-859d-f6bbb57e9c96
This commit is contained in:
Ingo Weinhold
2008-05-10 00:53:13 +00:00
parent 078d0317d0
commit c44fb2a62c
+73 -65
View File
@@ -27,6 +27,7 @@
#include <OS.h> #include <OS.h>
#include <StorageDefs.h> #include <StorageDefs.h>
#include <AutoDeleter.h>
#include <block_cache.h> #include <block_cache.h>
#include <boot/kernel_args.h> #include <boot/kernel_args.h>
#include <disk_device_manager/KDiskDevice.h> #include <disk_device_manager/KDiskDevice.h>
@@ -151,6 +152,21 @@ struct advisory_locking {
sem_id lock; sem_id lock;
sem_id wait_sem; sem_id wait_sem;
LockList locks; LockList locks;
advisory_locking()
:
lock(-1),
wait_sem(-1)
{
}
~advisory_locking()
{
if (lock >= 0)
delete_sem(lock);
if (wait_sem >= 0)
delete_sem(wait_sem);
}
}; };
static mutex sFileSystemsMutex; static mutex sFileSystemsMutex;
@@ -189,7 +205,7 @@ static mutex sVnodeCoveredByMutex;
/*! \brief Guards sVnodeTable. /*! \brief Guards sVnodeTable.
The holder is allowed to read/write access sVnodeTable and to The holder is allowed read/write access to sVnodeTable and to
any unbusy vnode in that table, save to the immutable fields (device, id, any unbusy vnode in that table, save to the immutable fields (device, id,
private_node, mount) to which private_node, mount) to which
only read-only access is allowed, and to the field covered_by, which is only read-only access is allowed, and to the field covered_by, which is
@@ -1122,42 +1138,38 @@ create_advisory_locking(struct vnode *vnode)
if (vnode == NULL) if (vnode == NULL)
return B_FILE_ERROR; return B_FILE_ERROR;
struct advisory_locking *locking = new(std::nothrow) advisory_locking; ObjectDeleter<advisory_locking> lockingDeleter;
if (locking == NULL) struct advisory_locking *locking = NULL;
return B_NO_MEMORY;
status_t status; while (get_advisory_locking(vnode) == NULL) {
// no locking object set on the vnode yet, create one
if (locking == NULL) {
locking = new(std::nothrow) advisory_locking;
if (locking == NULL)
return B_NO_MEMORY;
lockingDeleter.SetTo(locking);
locking->wait_sem = create_sem(0, "advisory lock"); locking->wait_sem = create_sem(0, "advisory lock");
if (locking->wait_sem < B_OK) { if (locking->wait_sem < B_OK)
status = locking->wait_sem; return locking->wait_sem;
goto err1;
}
locking->lock = create_sem(0, "advisory locking"); locking->lock = create_sem(0, "advisory locking");
if (locking->lock < B_OK) { if (locking->lock < B_OK)
status = locking->lock; return locking->lock;
goto err2; }
}
// We need to set the locking structure atomically - someone // set our newly created locking object
// else might set one at the same time MutexLocker _(sVnodeMutex);
do { if (vnode->advisory_locking == NULL) {
if (atomic_pointer_test_and_set(&vnode->advisory_locking, locking, vnode->advisory_locking = locking;
(advisory_locking*)NULL) == NULL) lockingDeleter.Detach();
return B_OK; return B_OK;
} while (get_advisory_locking(vnode) == NULL); }
}
status = B_OK; // The vnode already had a locking object. That's just as well.
// we delete the one we've just created, but nevertheless, the vnode
// does have a locking structure now
delete_sem(locking->lock); return B_OK;
err2:
delete_sem(locking->wait_sem);
err1:
delete locking;
return status;
} }
@@ -1287,9 +1299,12 @@ release_advisory_lock(struct vnode *vnode, struct flock *flock)
// longer used // longer used
locking = get_advisory_locking(vnode); locking = get_advisory_locking(vnode);
if (locking != NULL) { if (locking != NULL) {
MutexLocker locker(sVnodeMutex);
// the locking could have been changed in the mean time // the locking could have been changed in the mean time
if (locking->locks.IsEmpty()) { if (locking->locks.IsEmpty()) {
vnode->advisory_locking = NULL; vnode->advisory_locking = NULL;
locker.Unlock();
// we've detached the locking from the vnode, so we can // we've detached the locking from the vnode, so we can
// safely delete it // safely delete it
@@ -1298,6 +1313,7 @@ release_advisory_lock(struct vnode *vnode, struct flock *flock)
delete locking; delete locking;
} else { } else {
// the locking is in use again // the locking is in use again
locker.Unlock();
release_sem_etc(locking->lock, 1, B_DO_NOT_RESCHEDULE); release_sem_etc(locking->lock, 1, B_DO_NOT_RESCHEDULE);
} }
} }
@@ -1328,14 +1344,20 @@ acquire_advisory_lock(struct vnode *vnode, pid_t session, struct flock *flock,
// TODO: do deadlock detection! // TODO: do deadlock detection!
restart: struct advisory_locking *locking;
// if this vnode has an advisory_locking structure attached, sem_id waitForLock;
// lock that one and search for any colliding file lock
struct advisory_locking *locking = get_advisory_locking(vnode); while (true) {
team_id team = team_get_current_team_id(); // if this vnode has an advisory_locking structure attached,
sem_id waitForLock = -1; // lock that one and search for any colliding file lock
status = create_advisory_locking(vnode);
if (status != B_OK)
return status;
locking = vnode->advisory_locking;
team_id team = team_get_current_team_id();
waitForLock = -1;
if (locking != NULL) {
// test for collisions // test for collisions
LockList::Iterator iterator = locking->locks.GetIterator(); LockList::Iterator iterator = locking->locks.GetIterator();
while (iterator.HasNext()) { while (iterator.HasNext()) {
@@ -1352,41 +1374,27 @@ restart:
} }
} }
if (waitForLock < B_OK || !wait) if (waitForLock < 0)
break;
// We need to wait. Do that or fail now, if we've been asked not to.
if (!wait) {
put_advisory_locking(locking); put_advisory_locking(locking);
} return session != -1 ? B_WOULD_BLOCK : B_PERMISSION_DENIED;
// wait for the lock if we have to, or else return immediately
if (waitForLock >= B_OK) {
if (!wait)
status = session != -1 ? B_WOULD_BLOCK : B_PERMISSION_DENIED;
else {
status = switch_sem_etc(locking->lock, waitForLock, 1,
B_CAN_INTERRUPT, 0);
if (status == B_OK) {
// see if we're still colliding
goto restart;
}
} }
}
if (status < B_OK) status = switch_sem_etc(locking->lock, waitForLock, 1,
return status; B_CAN_INTERRUPT, 0);
if (status != B_OK && status != B_BAD_SEM_ID)
// install new lock
locking = get_advisory_locking(vnode);
if (locking == NULL) {
// we need to create a new locking object
status = create_advisory_locking(vnode);
if (status < B_OK)
return status; return status;
locking = vnode->advisory_locking; // We have been notified, but we need to re-lock the locking object. So
// we own the locking object, so it can't go away // go another round...
} }
// install new lock
struct advisory_lock *lock = (struct advisory_lock *)malloc( struct advisory_lock *lock = (struct advisory_lock *)malloc(
sizeof(struct advisory_lock)); sizeof(struct advisory_lock));
if (lock == NULL) { if (lock == NULL) {