mirror of
https://github.com/switchbrew/libnx.git
synced 2025-06-21 12:32:40 +02:00
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:
parent
4626b50341
commit
afe030f08b
@ -1,7 +1,7 @@
|
|||||||
/**
|
/**
|
||||||
* @file rwlock.h
|
* @file rwlock.h
|
||||||
* @brief Read/write lock synchronization primitive.
|
* @brief Read/write lock synchronization primitive.
|
||||||
* @author plutoo
|
* @author plutoo, SciresM
|
||||||
* @copyright libnx Authors
|
* @copyright libnx Authors
|
||||||
*/
|
*/
|
||||||
#pragma once
|
#pragma once
|
||||||
@ -11,10 +11,13 @@
|
|||||||
/// Read/write lock structure.
|
/// Read/write lock structure.
|
||||||
typedef struct {
|
typedef struct {
|
||||||
Mutex mutex;
|
Mutex mutex;
|
||||||
CondVar condvar_readers;
|
CondVar condvar_reader_wait;
|
||||||
CondVar condvar_writer;
|
CondVar condvar_writer_wait;
|
||||||
u32 readers : 31;
|
u32 read_lock_count;
|
||||||
bool writer : 1;
|
u32 read_waiter_count;
|
||||||
|
u32 write_lock_count;
|
||||||
|
u32 write_waiter_count;
|
||||||
|
u32 write_owner_tag;
|
||||||
} RwLock;
|
} RwLock;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -29,6 +32,13 @@ void rwlockInit(RwLock* r);
|
|||||||
*/
|
*/
|
||||||
void rwlockReadLock(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.
|
* @brief Unlocks the read/write lock for reading.
|
||||||
* @param r Read/write lock object.
|
* @param r Read/write lock object.
|
||||||
@ -41,8 +51,30 @@ void rwlockReadUnlock(RwLock* r);
|
|||||||
*/
|
*/
|
||||||
void rwlockWriteLock(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.
|
* @brief Unlocks the read/write lock for writing.
|
||||||
* @param r Read/write lock object.
|
* @param r Read/write lock object.
|
||||||
*/
|
*/
|
||||||
void rwlockWriteUnlock(RwLock* r);
|
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);
|
||||||
|
@ -1,59 +1,170 @@
|
|||||||
// Copyright 2018 plutoo
|
// Copyright 2018 plutoo
|
||||||
#include "kernel/mutex.h"
|
#include "kernel/mutex.h"
|
||||||
#include "kernel/rwlock.h"
|
#include "kernel/rwlock.h"
|
||||||
|
#include "../internal.h"
|
||||||
|
|
||||||
|
NX_INLINE u32 _GetCurrentThreadTag(void) {
|
||||||
|
return getThreadVars()->handle;
|
||||||
|
}
|
||||||
|
|
||||||
void rwlockInit(RwLock* r) {
|
void rwlockInit(RwLock* r) {
|
||||||
mutexInit(&r->mutex);
|
mutexInit(&r->mutex);
|
||||||
condvarInit(&r->condvar_readers);
|
condvarInit(&r->condvar_reader_wait);
|
||||||
condvarInit(&r->condvar_writer);
|
condvarInit(&r->condvar_writer_wait);
|
||||||
|
|
||||||
r->readers = 0;
|
r->read_lock_count = 0;
|
||||||
r->writer = false;
|
r->read_waiter_count = 0;
|
||||||
|
r->write_lock_count = 0;
|
||||||
|
r->write_waiter_count = 0;
|
||||||
|
r->write_owner_tag = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
void rwlockReadLock(RwLock* r) {
|
void rwlockReadLock(RwLock* r) {
|
||||||
mutexLock(&r->mutex);
|
const u32 cur_tag = _GetCurrentThreadTag();
|
||||||
|
|
||||||
while (r->writer) {
|
if (r->write_owner_tag == cur_tag) {
|
||||||
condvarWait(&r->condvar_writer, &r->mutex);
|
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);
|
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) {
|
void rwlockReadUnlock(RwLock* r) {
|
||||||
mutexLock(&r->mutex);
|
const u32 cur_tag = _GetCurrentThreadTag();
|
||||||
|
|
||||||
if (--r->readers == 0) {
|
if (r->write_owner_tag == cur_tag) {
|
||||||
condvarWakeAll(&r->condvar_readers);
|
// 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) {
|
void rwlockWriteLock(RwLock* r) {
|
||||||
|
const u32 cur_tag = _GetCurrentThreadTag();
|
||||||
|
|
||||||
|
if (r->write_owner_tag == cur_tag) {
|
||||||
|
r->write_lock_count++;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
mutexLock(&r->mutex);
|
mutexLock(&r->mutex);
|
||||||
|
|
||||||
while (r->writer) {
|
while (r->read_lock_count > 0) {
|
||||||
condvarWait(&r->condvar_writer, &r->mutex);
|
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) {
|
// mutexUnlock(&r->mutex) is intentionally not called here.
|
||||||
condvarWait(&r->condvar_readers, &r->mutex);
|
// 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) {
|
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;
|
if (r->write_waiter_count > 0) {
|
||||||
condvarWakeAll(&r->condvar_writer);
|
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();
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user