260 lines
4.9 KiB
C++
260 lines
4.9 KiB
C++
/*
|
|
* Copyright 2009, Ingo Weinhold, ingo_weinhold@gmx.de.
|
|
* Distributed under the terms of the MIT License.
|
|
*/
|
|
|
|
#include <RWLockManager.h>
|
|
|
|
#include <AutoLocker.h>
|
|
|
|
#include <syscalls.h>
|
|
#include <user_thread.h>
|
|
|
|
|
|
RWLockable::RWLockable()
|
|
:
|
|
fOwner(-1),
|
|
fOwnerCount(0),
|
|
fReaderCount(0)
|
|
{
|
|
}
|
|
|
|
|
|
RWLockManager::RWLockManager()
|
|
:
|
|
fLock("r/w lock manager")
|
|
{
|
|
}
|
|
|
|
|
|
RWLockManager::~RWLockManager()
|
|
{
|
|
}
|
|
|
|
|
|
bool
|
|
RWLockManager::ReadLock(RWLockable* lockable)
|
|
{
|
|
AutoLocker<RWLockManager> locker(this);
|
|
|
|
if (lockable->fWaiters.IsEmpty()) {
|
|
lockable->fReaderCount++;
|
|
return true;
|
|
}
|
|
|
|
return _Wait(lockable, false, B_INFINITE_TIMEOUT) == B_OK;
|
|
}
|
|
|
|
|
|
bool
|
|
RWLockManager::TryReadLock(RWLockable* lockable)
|
|
{
|
|
AutoLocker<RWLockManager> locker(this);
|
|
|
|
if (lockable->fWaiters.IsEmpty()) {
|
|
lockable->fReaderCount++;
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
status_t
|
|
RWLockManager::ReadLockWithTimeout(RWLockable* lockable, bigtime_t timeout)
|
|
{
|
|
AutoLocker<RWLockManager> locker(this);
|
|
|
|
if (lockable->fWaiters.IsEmpty()) {
|
|
lockable->fReaderCount++;
|
|
return B_OK;
|
|
}
|
|
|
|
return _Wait(lockable, false, timeout);
|
|
}
|
|
|
|
|
|
void
|
|
RWLockManager::ReadUnlock(RWLockable* lockable)
|
|
{
|
|
AutoLocker<RWLockManager> locker(this);
|
|
|
|
if (lockable->fReaderCount <= 0) {
|
|
debugger("RWLockManager::ReadUnlock(): Not read-locked!");
|
|
return;
|
|
}
|
|
|
|
if (--lockable->fReaderCount == 0)
|
|
_Unblock(lockable);
|
|
}
|
|
|
|
|
|
bool
|
|
RWLockManager::WriteLock(RWLockable* lockable)
|
|
{
|
|
AutoLocker<RWLockManager> locker(this);
|
|
|
|
thread_id thread = find_thread(NULL);
|
|
|
|
if (lockable->fOwner == thread) {
|
|
lockable->fOwnerCount++;
|
|
return true;
|
|
}
|
|
|
|
if (lockable->fReaderCount == 0 && lockable->fWaiters.IsEmpty()) {
|
|
lockable->fOwnerCount = 1;
|
|
lockable->fOwner = find_thread(NULL);
|
|
return true;
|
|
}
|
|
|
|
return _Wait(lockable, true, B_INFINITE_TIMEOUT) == B_OK;
|
|
}
|
|
|
|
|
|
bool
|
|
RWLockManager::TryWriteLock(RWLockable* lockable)
|
|
{
|
|
AutoLocker<RWLockManager> locker(this);
|
|
|
|
thread_id thread = find_thread(NULL);
|
|
|
|
if (lockable->fOwner == thread) {
|
|
lockable->fOwnerCount++;
|
|
return true;
|
|
}
|
|
|
|
if (lockable->fReaderCount == 0 && lockable->fWaiters.IsEmpty()) {
|
|
lockable->fOwnerCount++;
|
|
lockable->fOwner = thread;
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
status_t
|
|
RWLockManager::WriteLockWithTimeout(RWLockable* lockable, bigtime_t timeout)
|
|
{
|
|
AutoLocker<RWLockManager> locker(this);
|
|
|
|
thread_id thread = find_thread(NULL);
|
|
|
|
if (lockable->fOwner == thread) {
|
|
lockable->fOwnerCount++;
|
|
return B_OK;
|
|
}
|
|
|
|
if (lockable->fReaderCount == 0 && lockable->fWaiters.IsEmpty()) {
|
|
lockable->fOwnerCount++;
|
|
lockable->fOwner = thread;
|
|
return B_OK;
|
|
}
|
|
|
|
return _Wait(lockable, true, timeout);
|
|
}
|
|
|
|
|
|
void
|
|
RWLockManager::WriteUnlock(RWLockable* lockable)
|
|
{
|
|
AutoLocker<RWLockManager> locker(this);
|
|
|
|
if (find_thread(NULL) != lockable->fOwner) {
|
|
debugger("RWLockManager::WriteUnlock(): Not write-locked by calling "
|
|
"thread!");
|
|
return;
|
|
}
|
|
|
|
if (--lockable->fOwnerCount == 0) {
|
|
lockable->fOwner = -1;
|
|
_Unblock(lockable);
|
|
}
|
|
}
|
|
|
|
|
|
status_t
|
|
RWLockManager::_Wait(RWLockable* lockable, bool writer, bigtime_t timeout)
|
|
{
|
|
if (timeout == 0)
|
|
return B_TIMED_OUT;
|
|
|
|
// enqueue a waiter
|
|
RWLockable::Waiter waiter(writer);
|
|
lockable->fWaiters.Add(&waiter);
|
|
waiter.queued = true;
|
|
get_user_thread()->wait_status = 1;
|
|
|
|
// wait
|
|
Unlock();
|
|
|
|
status_t error;
|
|
do {
|
|
error = _kern_block_thread(
|
|
timeout >= 0 ? B_RELATIVE_TIMEOUT : 0, timeout);
|
|
// TODO: When interrupted we should adjust the timeout, respectively
|
|
// convert to an absolute timeout in the first place!
|
|
} while (error == B_INTERRUPTED);
|
|
|
|
Lock();
|
|
|
|
if (!waiter.queued)
|
|
return waiter.status;
|
|
|
|
// we're still queued, which means an error (timeout, interrupt)
|
|
// occurred
|
|
lockable->fWaiters.Remove(&waiter);
|
|
|
|
_Unblock(lockable);
|
|
|
|
return error;
|
|
}
|
|
|
|
|
|
void
|
|
RWLockManager::_Unblock(RWLockable* lockable)
|
|
{
|
|
// Check whether there any waiting threads at all and whether anyone
|
|
// has the write lock
|
|
RWLockable::Waiter* waiter = lockable->fWaiters.Head();
|
|
if (waiter == NULL || lockable->fOwner >= 0)
|
|
return;
|
|
|
|
// writer at head of queue?
|
|
if (waiter->writer) {
|
|
if (lockable->fReaderCount == 0) {
|
|
waiter->status = B_OK;
|
|
waiter->queued = false;
|
|
lockable->fWaiters.Remove(waiter);
|
|
lockable->fOwner = waiter->thread;
|
|
lockable->fOwnerCount = 1;
|
|
|
|
_kern_unblock_thread(waiter->thread, B_OK);
|
|
}
|
|
return;
|
|
}
|
|
|
|
// wake up one or more readers -- we unblock more than one reader at
|
|
// a time to save trips to the kernel
|
|
while (!lockable->fWaiters.IsEmpty()
|
|
&& !lockable->fWaiters.Head()->writer) {
|
|
static const int kMaxReaderUnblockCount = 128;
|
|
thread_id readers[kMaxReaderUnblockCount];
|
|
int readerCount = 0;
|
|
|
|
while (readerCount < kMaxReaderUnblockCount
|
|
&& (waiter = lockable->fWaiters.Head()) != NULL
|
|
&& !waiter->writer) {
|
|
waiter->status = B_OK;
|
|
waiter->queued = false;
|
|
lockable->fWaiters.Remove(waiter);
|
|
|
|
readers[readerCount++] = waiter->thread;
|
|
lockable->fReaderCount++;
|
|
}
|
|
|
|
if (readerCount > 0)
|
|
_kern_unblock_threads(readers, readerCount, B_OK);
|
|
}
|
|
}
|