Implement rw_try_upgrade() and rw_downgrade(). rw_try_upgrade() makes a

single attempt at upgrading a read lock to a write lock, and rw_downgrade()
converts curthread's write lock into a read lock.
This commit is contained in:
John Baldwin 2006-04-19 21:06:52 +00:00
parent 0b79fedb9b
commit fea3efe5bf
2 changed files with 138 additions and 3 deletions

View File

@ -55,7 +55,7 @@ static void db_show_rwlock(struct lock_object *lock);
struct lock_class lock_class_rw = {
"rw",
LC_SLEEPLOCK | LC_RECURSABLE /* | LC_UPGRADABLE */,
LC_SLEEPLOCK | LC_RECURSABLE | LC_UPGRADABLE,
#ifdef DDB
db_show_rwlock
#endif
@ -87,7 +87,7 @@ rw_init(struct rwlock *rw, const char *name)
rw->rw_lock = RW_UNLOCKED;
lock_init(&rw->rw_object, &lock_class_rw, name, NULL, LO_WITNESS |
LO_RECURSABLE /* | LO_UPGRADABLE */);
LO_RECURSABLE | LO_UPGRADABLE);
}
void
@ -585,6 +585,137 @@ _rw_wunlock_hard(struct rwlock *rw, uintptr_t tid, const char *file, int line)
turnstile_unpend(ts, TS_EXCLUSIVE_LOCK);
}
/*
* Attempt to do a non-blocking upgrade from a read lock to a write
* lock. This will only succeed if this thread holds a single read
* lock. Returns true if the upgrade succeeded and false otherwise.
*/
int
_rw_try_upgrade(struct rwlock *rw, const char *file, int line)
{
uintptr_t v, tid;
int success;
_rw_assert(rw, RA_RLOCKED, file, line);
/*
* Attempt to switch from one reader to a writer. If there
* are any write waiters, then we will have to lock the
* turnstile first to prevent races with another writer
* calling turnstile_wait() before we have claimed this
* turnstile. So, do the simple case of no waiters first.
*/
tid = (uintptr_t)curthread;
if (!(rw->rw_lock & RW_LOCK_WRITE_WAITERS)) {
success = atomic_cmpset_acq_ptr(&rw->rw_lock,
RW_READERS_LOCK(1), tid);
goto out;
}
/*
* Ok, we think we have write waiters, so lock the
* turnstile.
*/
turnstile_lock(&rw->rw_object);
/*
* Try to switch from one reader to a writer again. This time
* we honor the current state of the RW_LOCK_WRITE_WAITERS
* flag. If we obtain the lock with the flag set, then claim
* ownership of the turnstile. In the SMP case it is possible
* for there to not be an associated turnstile even though there
* are waiters if all of the waiters are spinning.
*/
v = rw->rw_lock & RW_LOCK_WRITE_WAITERS;
success = atomic_cmpset_acq_ptr(&rw->rw_lock, RW_READERS_LOCK(1) | v,
tid | v);
#ifdef SMP
if (success && v && turnstile_lookup(&rw->rw_object) != NULL)
#else
if (success && v)
#endif
turnstile_claim(&rw->rw_object);
else
turnstile_release(&rw->rw_object);
out:
LOCK_LOG_TRY("WUPGRADE", &rw->rw_object, 0, success, file, line);
if (success)
WITNESS_UPGRADE(&rw->rw_object, LOP_EXCLUSIVE | LOP_TRYLOCK,
file, line);
return (success);
}
/*
* Downgrade a write lock into a single read lock.
*/
void
_rw_downgrade(struct rwlock *rw, const char *file, int line)
{
struct turnstile *ts;
uintptr_t tid, v;
_rw_assert(rw, RA_WLOCKED, file, line);
WITNESS_DOWNGRADE(&rw->rw_object, 0, file, line);
/*
* Convert from a writer to a single reader. First we handle
* the easy case with no waiters. If there are any waiters, we
* lock the turnstile, "disown" the lock, and awaken any read
* waiters.
*/
tid = (uintptr_t)curthread;
if (atomic_cmpset_rel_ptr(&rw->rw_lock, tid, RW_READERS_LOCK(1)))
goto out;
/*
* Ok, we think we have waiters, so lock the turnstile so we can
* read the waiter flags without any races.
*/
turnstile_lock(&rw->rw_object);
v = rw->rw_lock;
MPASS(v & (RW_LOCK_READ_WAITERS | RW_LOCK_WRITE_WAITERS));
/*
* Downgrade from a write lock while preserving
* RW_LOCK_WRITE_WAITERS and give up ownership of the
* turnstile. If there are any read waiters, wake them up.
*
* For SMP, we have to allow for the fact that all of the
* read waiters might be spinning. In that case, act as if
* RW_LOCK_READ_WAITERS is not set. Also, only preserve
* the RW_LOCK_WRITE_WAITERS flag if at least one writer is
* blocked on the turnstile.
*/
ts = turnstile_lookup(&rw->rw_object);
#ifdef SMP
if (ts == NULL)
v &= ~(RW_LOCK_READ_WAITERS | RW_LOCK_WRITE_WAITERS);
else if (v & RW_LOCK_READ_WAITERS &&
turnstile_empty(ts, TS_SHARED_QUEUE))
v &= ~RW_LOCK_READ_WAITERS;
else if (v & RW_LOCK_WRITE_WAITERS &&
turnstile_empty(ts, TS_EXCLUSIVE_QUEUE))
v &= ~RW_LOCK_WRITE_WAITERS;
#else
MPASS(ts != NULL);
#endif
if (v & RW_LOCK_READ_WAITERS)
turnstile_broadcast(ts, TS_SHARED_QUEUE);
atomic_store_rel_ptr(&rw->rw_lock, RW_READERS_LOCK(1) |
(v & RW_LOCK_WRITE_WAITERS));
if (v & RW_LOCK_READ_WAITERS)
turnstile_unpend(ts, TS_EXCLUSIVE_LOCK);
#ifdef SMP
else if (ts == NULL)
turnstile_release(&rw->rw_object);
#endif
else
turnstile_disown(ts);
out:
LOCK_LOG_LOCK("WDOWNGRADE", &rw->rw_object, 0, 0, file, line);
}
#ifdef INVARIANT_SUPPORT
#ifndef INVARIANTS
#undef _rw_assert

View File

@ -126,6 +126,8 @@ void _rw_wlock_hard(struct rwlock *rw, uintptr_t tid, const char *file,
int line);
void _rw_wunlock_hard(struct rwlock *rw, uintptr_t tid, const char *file,
int line);
int _rw_try_upgrade(struct rwlock *rw, const char *file, int line);
void _rw_downgrade(struct rwlock *rw, const char *file, int line);
#if defined(INVARIANTS) || defined(INVARIANT_SUPPORT)
void _rw_assert(struct rwlock *rw, int what, const char *file, int line);
#endif
@ -133,7 +135,7 @@ void _rw_assert(struct rwlock *rw, int what, const char *file, int line);
/*
* Public interface for lock operations.
*
* XXX: Missing try and upgrade/downgrade.
* XXX: Missing try locks.
*/
#ifndef LOCK_DEBUG
@ -150,6 +152,8 @@ void _rw_assert(struct rwlock *rw, int what, const char *file, int line);
#endif
#define rw_rlock(rw) _rw_rlock((rw), LOCK_FILE, LOCK_LINE)
#define rw_runlock(rw) _rw_runlock((rw), LOCK_FILE, LOCK_LINE)
#define rw_try_upgrade(rw) _rw_try_upgrade((rw), LOCK_FILE, LOCK_LINE)
#define rw_downgrade(rw) _rw_downgrade((rw), LOCK_FILE, LOCK_LINE)
#define rw_initialized(rw) lock_initalized(&(rw)->rw_object)