haiku/src/kits/shared/RWLockManager.cpp

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);
}
}