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:
parent
078d0317d0
commit
c44fb2a62c
@ -27,6 +27,7 @@
|
||||
#include <OS.h>
|
||||
#include <StorageDefs.h>
|
||||
|
||||
#include <AutoDeleter.h>
|
||||
#include <block_cache.h>
|
||||
#include <boot/kernel_args.h>
|
||||
#include <disk_device_manager/KDiskDevice.h>
|
||||
@ -151,6 +152,21 @@ struct advisory_locking {
|
||||
sem_id lock;
|
||||
sem_id wait_sem;
|
||||
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;
|
||||
@ -189,7 +205,7 @@ static mutex sVnodeCoveredByMutex;
|
||||
|
||||
/*! \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,
|
||||
private_node, mount) to which
|
||||
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)
|
||||
return B_FILE_ERROR;
|
||||
|
||||
struct advisory_locking *locking = new(std::nothrow) advisory_locking;
|
||||
if (locking == NULL)
|
||||
return B_NO_MEMORY;
|
||||
ObjectDeleter<advisory_locking> lockingDeleter;
|
||||
struct advisory_locking *locking = NULL;
|
||||
|
||||
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");
|
||||
if (locking->wait_sem < B_OK) {
|
||||
status = locking->wait_sem;
|
||||
goto err1;
|
||||
}
|
||||
locking->wait_sem = create_sem(0, "advisory lock");
|
||||
if (locking->wait_sem < B_OK)
|
||||
return locking->wait_sem;
|
||||
|
||||
locking->lock = create_sem(0, "advisory locking");
|
||||
if (locking->lock < B_OK) {
|
||||
status = locking->lock;
|
||||
goto err2;
|
||||
}
|
||||
locking->lock = create_sem(0, "advisory locking");
|
||||
if (locking->lock < B_OK)
|
||||
return locking->lock;
|
||||
}
|
||||
|
||||
// We need to set the locking structure atomically - someone
|
||||
// else might set one at the same time
|
||||
do {
|
||||
if (atomic_pointer_test_and_set(&vnode->advisory_locking, locking,
|
||||
(advisory_locking*)NULL) == NULL)
|
||||
// set our newly created locking object
|
||||
MutexLocker _(sVnodeMutex);
|
||||
if (vnode->advisory_locking == NULL) {
|
||||
vnode->advisory_locking = locking;
|
||||
lockingDeleter.Detach();
|
||||
return B_OK;
|
||||
} while (get_advisory_locking(vnode) == NULL);
|
||||
}
|
||||
}
|
||||
|
||||
status = B_OK;
|
||||
// we delete the one we've just created, but nevertheless, the vnode
|
||||
// does have a locking structure now
|
||||
// The vnode already had a locking object. That's just as well.
|
||||
|
||||
delete_sem(locking->lock);
|
||||
err2:
|
||||
delete_sem(locking->wait_sem);
|
||||
err1:
|
||||
delete locking;
|
||||
return status;
|
||||
return B_OK;
|
||||
}
|
||||
|
||||
|
||||
@ -1287,9 +1299,12 @@ release_advisory_lock(struct vnode *vnode, struct flock *flock)
|
||||
// longer used
|
||||
locking = get_advisory_locking(vnode);
|
||||
if (locking != NULL) {
|
||||
MutexLocker locker(sVnodeMutex);
|
||||
|
||||
// the locking could have been changed in the mean time
|
||||
if (locking->locks.IsEmpty()) {
|
||||
vnode->advisory_locking = NULL;
|
||||
locker.Unlock();
|
||||
|
||||
// we've detached the locking from the vnode, so we can
|
||||
// safely delete it
|
||||
@ -1298,6 +1313,7 @@ release_advisory_lock(struct vnode *vnode, struct flock *flock)
|
||||
delete locking;
|
||||
} else {
|
||||
// the locking is in use again
|
||||
locker.Unlock();
|
||||
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!
|
||||
|
||||
restart:
|
||||
// if this vnode has an advisory_locking structure attached,
|
||||
// lock that one and search for any colliding file lock
|
||||
struct advisory_locking *locking = get_advisory_locking(vnode);
|
||||
team_id team = team_get_current_team_id();
|
||||
sem_id waitForLock = -1;
|
||||
struct advisory_locking *locking;
|
||||
sem_id waitForLock;
|
||||
|
||||
while (true) {
|
||||
// if this vnode has an advisory_locking structure attached,
|
||||
// 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
|
||||
LockList::Iterator iterator = locking->locks.GetIterator();
|
||||
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);
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
return session != -1 ? B_WOULD_BLOCK : B_PERMISSION_DENIED;
|
||||
}
|
||||
}
|
||||
|
||||
if (status < B_OK)
|
||||
return status;
|
||||
|
||||
// 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)
|
||||
status = switch_sem_etc(locking->lock, waitForLock, 1,
|
||||
B_CAN_INTERRUPT, 0);
|
||||
if (status != B_OK && status != B_BAD_SEM_ID)
|
||||
return status;
|
||||
|
||||
locking = vnode->advisory_locking;
|
||||
// we own the locking object, so it can't go away
|
||||
// We have been notified, but we need to re-lock the locking object. So
|
||||
// go another round...
|
||||
}
|
||||
|
||||
// install new lock
|
||||
|
||||
struct advisory_lock *lock = (struct advisory_lock *)malloc(
|
||||
sizeof(struct advisory_lock));
|
||||
if (lock == NULL) {
|
||||
|
Loading…
Reference in New Issue
Block a user