diff --git a/nx/include/switch/kernel/rwlock.h b/nx/include/switch/kernel/rwlock.h index bf23edaa..731e95cd 100644 --- a/nx/include/switch/kernel/rwlock.h +++ b/nx/include/switch/kernel/rwlock.h @@ -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); diff --git a/nx/source/kernel/rwlock.c b/nx/source/kernel/rwlock.c index 1da465ad..8a5e19be 100644 --- a/nx/source/kernel/rwlock.c +++ b/nx/source/kernel/rwlock.c @@ -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); } - - 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; } - mutexUnlock(&r->mutex); + 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); + } - mutexUnlock(&r->mutex); + // 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(); }