diff --git a/headers/private/shared/RWLockManager.h b/headers/private/shared/RWLockManager.h new file mode 100644 index 0000000000..de62004a29 --- /dev/null +++ b/headers/private/shared/RWLockManager.h @@ -0,0 +1,131 @@ +/* + * Copyright 2009, Ingo Weinhold, ingo_weinhold@gmx.de. + * Distributed under the terms of the MIT License. + */ +#ifndef _RW_LOCK_MANAGER_H +#define _RW_LOCK_MANAGER_H + +#include + +#include + + +namespace BPrivate { + + +class RWLockManager; + + +class RWLockable { +public: + RWLockable(); + +private: + struct Waiter : DoublyLinkedListLinkImpl { + Waiter(bool writer) + : + thread(find_thread(NULL)), + writer(writer), + queued(false) + { + } + + thread_id thread; + status_t status; + bool writer; + bool queued; + }; + + typedef DoublyLinkedList WaiterList; + + friend class RWLockManager; + +private: + thread_id fOwner; + int32 fOwnerCount; + int32 fReaderCount; + WaiterList fWaiters; +}; + + +class RWLockManager { +public: + RWLockManager(); + ~RWLockManager(); + + status_t Init() { return fLock.InitCheck(); } + + bool Lock() { return fLock.Lock(); } + void Unlock() { return fLock.Unlock(); } + + bool ReadLock(RWLockable* lockable); + bool TryReadLock(RWLockable* lockable); + status_t ReadLockWithTimeout(RWLockable* lockable, + bigtime_t timeout); + void ReadUnlock(RWLockable* lockable); + + bool WriteLock(RWLockable* lockable); + bool TryWriteLock(RWLockable* lockable); + status_t WriteLockWithTimeout(RWLockable* lockable, + bigtime_t timeout); + void WriteUnlock(RWLockable* lockable); + + inline bool GenericLock(bool write, RWLockable* lockable); + inline bool TryGenericLock(bool write, + RWLockable* lockable); + inline status_t GenericLockWithTimeout(bool write, + RWLockable* lockable, bigtime_t timeout); + inline void GenericUnlock(bool write, RWLockable* lockable); + +private: + status_t _Wait(RWLockable* lockable, bool writer, + bigtime_t timeout); + void _Unblock(RWLockable* lockable); + +private: + BLocker fLock; +}; + + +inline bool +RWLockManager::GenericLock(bool write, RWLockable* lockable) +{ + return write ? WriteLock(lockable) : ReadLock(lockable); +} + + +inline bool +RWLockManager::TryGenericLock(bool write, RWLockable* lockable) +{ + return write ? TryWriteLock(lockable) : TryReadLock(lockable); +} + + +inline status_t +RWLockManager::GenericLockWithTimeout(bool write, RWLockable* lockable, + bigtime_t timeout) +{ + return write + ? WriteLockWithTimeout(lockable, timeout) + : ReadLockWithTimeout(lockable, timeout); +} + + +inline void +RWLockManager::GenericUnlock(bool write, RWLockable* lockable) +{ + if (write) + WriteUnlock(lockable); + else + ReadUnlock(lockable); +} + + +} // namespace BPrivate + + +using BPrivate::RWLockable; +using BPrivate::RWLockManager; + + +#endif // _RW_LOCK_MANAGER_H diff --git a/src/kits/shared/Jamfile b/src/kits/shared/Jamfile index b6b26ad03f..5a010c3f9f 100644 --- a/src/kits/shared/Jamfile +++ b/src/kits/shared/Jamfile @@ -6,6 +6,10 @@ AddSubDirSupportedPlatforms libbe_test ; UseLibraryHeaders agg ; UsePrivateHeaders shared ; +# for RWLockManager only +UsePrivateSystemHeaders ; +UsePrivateHeaders kernel libroot ; + StaticLibrary libshared.a : AboutWindow.cpp AffineTransform.cpp @@ -14,4 +18,5 @@ StaticLibrary libshared.a : CommandPipe.cpp DateTime.cpp HashString.cpp + RWLockManager.cpp ; diff --git a/src/kits/shared/RWLockManager.cpp b/src/kits/shared/RWLockManager.cpp new file mode 100644 index 0000000000..fe306c5f3e --- /dev/null +++ b/src/kits/shared/RWLockManager.cpp @@ -0,0 +1,259 @@ +/* + * Copyright 2009, Ingo Weinhold, ingo_weinhold@gmx.de. + * Distributed under the terms of the MIT License. + */ + +#include + +#include + +#include +#include + + +RWLockable::RWLockable() + : + fOwner(-1), + fOwnerCount(0), + fReaderCount(0) +{ +} + + +RWLockManager::RWLockManager() + : + fLock("r/w lock manager") +{ +} + + +RWLockManager::~RWLockManager() +{ +} + + +bool +RWLockManager::ReadLock(RWLockable* lockable) +{ + AutoLocker 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 locker(this); + + if (lockable->fWaiters.IsEmpty()) { + lockable->fReaderCount++; + return true; + } + + return false; +} + + +status_t +RWLockManager::ReadLockWithTimeout(RWLockable* lockable, bigtime_t timeout) +{ + AutoLocker locker(this); + + if (lockable->fWaiters.IsEmpty()) { + lockable->fReaderCount++; + return B_OK; + } + + return _Wait(lockable, false, timeout); +} + + +void +RWLockManager::ReadUnlock(RWLockable* lockable) +{ + AutoLocker 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 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 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 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 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); + } +}