rwlock: revamp completely (#350)

Implements rwlockTryReadLock and rwlockTryWriteLock.
Also implements rwlockIsWriteLockHeldByCurrentThread and
rwlockIsOwnedByCurrentThread.

Also re-designs RwLock to have semantics identical to
Nintendo's (nn::os::ReaderWriterLock). The upshot is mostly
that the lock is now fully recursive/write-preferring.
This commit is contained in:
SciresM 2019-12-03 14:16:40 -08:00 committed by fincs
parent 4626b50341
commit afe030f08b
2 changed files with 171 additions and 28 deletions

View File

@ -1,7 +1,7 @@
/**
* @file rwlock.h
* @brief Read/write lock synchronization primitive.
* @author plutoo
* @author plutoo, SciresM
* @copyright libnx Authors
*/
#pragma once
@ -11,10 +11,13 @@
/// Read/write lock structure.
typedef struct {
Mutex mutex;
CondVar condvar_readers;
CondVar condvar_writer;
u32 readers : 31;
bool writer : 1;
CondVar condvar_reader_wait;
CondVar condvar_writer_wait;
u32 read_lock_count;
u32 read_waiter_count;
u32 write_lock_count;
u32 write_waiter_count;
u32 write_owner_tag;
} RwLock;
/**
@ -29,6 +32,13 @@ void rwlockInit(RwLock* r);
*/
void rwlockReadLock(RwLock* r);
/**
* @brief Attempts to lock the read/write lock for reading without waiting.
* @param r Read/write lock object.
* @return 1 if the mutex has been acquired successfully, and 0 on contention.
*/
bool rwlockTryReadLock(RwLock* r);
/**
* @brief Unlocks the read/write lock for reading.
* @param r Read/write lock object.
@ -41,8 +51,30 @@ void rwlockReadUnlock(RwLock* r);
*/
void rwlockWriteLock(RwLock* r);
/**
* @brief Attempts to lock the read/write lock for writing without waiting.
* @param r Read/write lock object.
* @return 1 if the mutex has been acquired successfully, and 0 on contention.
*/
bool rwlockTryWriteLock(RwLock* r);
/**
* @brief Unlocks the read/write lock for writing.
* @param r Read/write lock object.
*/
void rwlockWriteUnlock(RwLock* r);
/**
* @brief Checks if the write lock is held by the current thread.
* @param r Read/write lock object.
* @return 1 if the current hold holds the write lock, and 0 if it does not.
*/
bool rwlockIsWriteLockHeldByCurrentThread(RwLock* r);
/**
* @brief Checks if the read/write lock is owned by the current thread.
* @param r Read/write lock object.
* @return 1 if the current hold holds the write lock or if it holds read locks acquired
* while it held the write lock, and 0 if it does not.
*/
bool rwlockIsOwnedByCurrentThread(RwLock* r);

View File

@ -1,59 +1,170 @@
// Copyright 2018 plutoo
#include "kernel/mutex.h"
#include "kernel/rwlock.h"
#include "../internal.h"
NX_INLINE u32 _GetCurrentThreadTag(void) {
return getThreadVars()->handle;
}
void rwlockInit(RwLock* r) {
mutexInit(&r->mutex);
condvarInit(&r->condvar_readers);
condvarInit(&r->condvar_writer);
condvarInit(&r->condvar_reader_wait);
condvarInit(&r->condvar_writer_wait);
r->readers = 0;
r->writer = false;
r->read_lock_count = 0;
r->read_waiter_count = 0;
r->write_lock_count = 0;
r->write_waiter_count = 0;
r->write_owner_tag = 0;
}
void rwlockReadLock(RwLock* r) {
mutexLock(&r->mutex);
const u32 cur_tag = _GetCurrentThreadTag();
while (r->writer) {
condvarWait(&r->condvar_writer, &r->mutex);
if (r->write_owner_tag == cur_tag) {
r->read_lock_count++;
return;
}
r->readers++;
mutexLock(&r->mutex);
while (r->write_waiter_count > 0) {
r->read_waiter_count++;
condvarWait(&r->condvar_reader_wait, &r->mutex);
r->read_waiter_count--;
}
r->read_lock_count++;
mutexUnlock(&r->mutex);
}
bool rwlockTryReadLock(RwLock* r) {
const u32 cur_tag = _GetCurrentThreadTag();
if (r->write_owner_tag == cur_tag) {
r->read_lock_count++;
return true;
}
if (!mutexTryLock(&r->mutex)) {
return false;
}
const bool got_lock = r->write_waiter_count == 0;
if (got_lock) {
r->read_lock_count++;
}
mutexUnlock(&r->mutex);
return got_lock;
}
void rwlockReadUnlock(RwLock* r) {
mutexLock(&r->mutex);
const u32 cur_tag = _GetCurrentThreadTag();
if (--r->readers == 0) {
condvarWakeAll(&r->condvar_readers);
if (r->write_owner_tag == cur_tag) {
// Write lock is owned by this thread.
r->read_lock_count--;
if (r->read_lock_count == 0 && r->write_lock_count == 0) {
// Relinquish control of the lock.
r->write_owner_tag = 0;
if (r->write_waiter_count > 0) {
condvarWakeOne(&r->condvar_writer_wait);
} else if (r->read_waiter_count > 0) {
condvarWakeAll(&r->condvar_reader_wait);
}
// Corresponding mutexLock was called in WriteLock/WriteTryLock.
mutexUnlock(&r->mutex);
}
} else {
// Write lock isn't owned by this thread.
mutexLock(&r->mutex);
r->read_lock_count--;
if (r->read_lock_count == 0 && r->write_waiter_count > 0) {
condvarWakeOne(&r->condvar_writer_wait);
}
mutexUnlock(&r->mutex);
}
}
void rwlockWriteLock(RwLock* r) {
const u32 cur_tag = _GetCurrentThreadTag();
if (r->write_owner_tag == cur_tag) {
r->write_lock_count++;
return;
}
mutexLock(&r->mutex);
while (r->writer) {
condvarWait(&r->condvar_writer, &r->mutex);
while (r->read_lock_count > 0) {
r->write_waiter_count++;
condvarWait(&r->condvar_writer_wait, &r->mutex);
r->write_waiter_count--;
}
r->writer = true;
r->write_lock_count = 1;
r->write_owner_tag = cur_tag;
while (r->readers > 0) {
condvarWait(&r->condvar_readers, &r->mutex);
// mutexUnlock(&r->mutex) is intentionally not called here.
// The mutex will be unlocked by a call to ReadUnlock or WriteUnlock.
}
bool rwlockTryWriteLock(RwLock* r) {
const u32 cur_tag = _GetCurrentThreadTag();
if (r->write_owner_tag == cur_tag) {
r->write_lock_count++;
return true;
}
if (!mutexTryLock(&r->mutex)) {
return false;
}
if (r->read_lock_count > 0) {
mutexUnlock(&r->mutex);
return false;
}
r->write_lock_count = 1;
r->write_owner_tag = cur_tag;
// mutexUnlock(&r->mutex) is intentionally not called here.
// The mutex will be unlocked by a call to ReadUnlock or WriteUnlock.
return true;
}
void rwlockWriteUnlock(RwLock* r) {
mutexLock(&r->mutex);
// This function assumes the write lock is held.
// This means that r->mutex is locked, and r->write_owner_tag == cur_tag.
// if (r->write_owner_tag == cur_tag)
{
r->write_lock_count--;
if (r->write_lock_count == 0 && r->read_lock_count == 0) {
// Relinquish control of the lock.
r->write_owner_tag = 0;
r->writer = false;
condvarWakeAll(&r->condvar_writer);
if (r->write_waiter_count > 0) {
condvarWakeOne(&r->condvar_writer_wait);
} else if (r->read_waiter_count > 0) {
condvarWakeAll(&r->condvar_reader_wait);
}
// Corresponding mutexLock was called in WriteLock/WriteTryLock.
mutexUnlock(&r->mutex);
}
}
}
bool rwlockIsWriteLockHeldByCurrentThread(RwLock* r) {
return r->write_owner_tag == _GetCurrentThreadTag() && r->write_lock_count > 0;
}
bool rwlockIsOwnedByCurrentThread(RwLock* r) {
return r->write_owner_tag == _GetCurrentThreadTag();
}