From be433342affcd22a76e10f478d66325cc5656165 Mon Sep 17 00:00:00 2001 From: Ingo Weinhold Date: Tue, 10 Jun 2003 22:32:33 +0000 Subject: [PATCH] A read-/write locker. Again, something that might also be useful elsewhere in the kernel. git-svn-id: file:///srv/svn/repos/haiku/trunk/current@3457 a95241bf-73f2-0310-859d-f6bbb57e9c96 --- .../core/disk_device_manager/RWLocker.cpp | 504 ++++++++++++++++++ .../core/disk_device_manager/RWLocker.h | 129 +++++ 2 files changed, 633 insertions(+) create mode 100644 src/kernel/core/disk_device_manager/RWLocker.cpp create mode 100644 src/kernel/core/disk_device_manager/RWLocker.h diff --git a/src/kernel/core/disk_device_manager/RWLocker.cpp b/src/kernel/core/disk_device_manager/RWLocker.cpp new file mode 100644 index 0000000000..0449be8873 --- /dev/null +++ b/src/kernel/core/disk_device_manager/RWLocker.cpp @@ -0,0 +1,504 @@ +// RWLocker.cpp + +#include + +#include "RWLocker.h" + +using namespace std; + +// info about a read lock owner +struct RWLocker::ReadLockInfo { + thread_id reader; + int32 count; +}; + + +// constructor +RWLocker::RWLocker() + : fLock(), + fMutex(), + fQueue(), + fReaderCount(0), + fWriterCount(0), + fReadLockInfos(8), + fWriter(B_ERROR), + fWriterWriterCount(0), + fWriterReaderCount(0) +{ + _Init(NULL); +} + +// constructor +RWLocker::RWLocker(const char* name) + : fLock(), + fMutex(), + fQueue(), + fReaderCount(0), + fWriterCount(0), + fReadLockInfos(8), + fWriter(B_ERROR), + fWriterWriterCount(0), + fWriterReaderCount(0) +{ + _Init(name); +} + +// destructor +RWLocker::~RWLocker() +{ + _AcquireBenaphore(fLock); + delete_sem(fMutex.semaphore); + delete_sem(fQueue.semaphore); + for (int32 i = 0; ReadLockInfo* info = _ReadLockInfoAt(i); i++) + delete info; + delete_sem(fLock.semaphore); +} + +// InitCheck +status_t +RWLocker::InitCheck() const +{ + if (fLock.semaphore < 0) + return fLock.semaphore; + if (fMutex.semaphore < 0) + return fMutex.semaphore; + if (fQueue.semaphore < 0) + return fQueue.semaphore; + return B_OK; +} + +// ReadLock +bool +RWLocker::ReadLock() +{ + status_t error = _ReadLock(B_INFINITE_TIMEOUT); + return (error == B_OK); +} + +// ReadLockWithTimeout +status_t +RWLocker::ReadLockWithTimeout(bigtime_t timeout) +{ + bigtime_t absoluteTimeout = system_time() + timeout; + // take care of overflow + if (timeout > 0 && absoluteTimeout < 0) + absoluteTimeout = B_INFINITE_TIMEOUT; + return _ReadLock(absoluteTimeout); +} + +// ReadUnlock +void +RWLocker::ReadUnlock() +{ + if (_AcquireBenaphore(fLock) == B_OK) { + thread_id thread = find_thread(NULL); + if (thread == fWriter) { + // We (also) have a write lock. + if (fWriterReaderCount > 0) + fWriterReaderCount--; + // else: error: unmatched ReadUnlock() + } else { + int32 index = _IndexOf(thread); + if (ReadLockInfo* info = _ReadLockInfoAt(index)) { + fReaderCount--; + if (--info->count == 0) { + // The outer read lock bracket for the thread has been + // reached. Dispose the info. + _DeleteReadLockInfo(index); + } + if (fReaderCount == 0) { + // The last reader needs to unlock the mutex. + _ReleaseBenaphore(fMutex); + } + } // else: error: caller has no read lock + } + _ReleaseBenaphore(fLock); + } // else: we are probably going to be destroyed +} + +// IsReadLocked +// +// Returns whether or not the calling thread owns a read lock or even a +// write lock. +bool +RWLocker::IsReadLocked() const +{ + bool result = false; + if (_AcquireBenaphore(fLock) == B_OK) { + thread_id thread = find_thread(NULL); + result = (thread == fWriter || _IndexOf(thread) >= 0); + _ReleaseBenaphore(fLock); + } + return result; +} + +// WriteLock +bool +RWLocker::WriteLock() +{ + status_t error = _WriteLock(B_INFINITE_TIMEOUT); + return (error == B_OK); +} + +// WriteLockWithTimeout +status_t +RWLocker::WriteLockWithTimeout(bigtime_t timeout) +{ + bigtime_t absoluteTimeout = system_time() + timeout; + // take care of overflow + if (timeout > 0 && absoluteTimeout < 0) + absoluteTimeout = B_INFINITE_TIMEOUT; + return _WriteLock(absoluteTimeout); +} + +// WriteUnlock +void +RWLocker::WriteUnlock() +{ + if (_AcquireBenaphore(fLock) == B_OK) { + thread_id thread = find_thread(NULL); + if (thread == fWriter) { + fWriterCount--; + if (--fWriterWriterCount == 0) { + // The outer write lock bracket for the thread has been + // reached. + fWriter = B_ERROR; + if (fWriterReaderCount > 0) { + // We still own read locks. + _NewReadLockInfo(thread, fWriterReaderCount); + // A reader that expects to be the first reader may wait + // at the mutex semaphore. We need to wake it up. + if (fReaderCount > 0) + _ReleaseBenaphore(fMutex); + fReaderCount += fWriterReaderCount; + fWriterReaderCount = 0; + } else { + // We don't own any read locks. So we have to release the + // mutex benaphore. + _ReleaseBenaphore(fMutex); + } + } + } // else: error: unmatched WriteUnlock() + _ReleaseBenaphore(fLock); + } // else: We're probably going to die. +} + +// IsWriteLocked +// +// Returns whether or not the calling thread owns a write lock. +bool +RWLocker::IsWriteLocked() const +{ + return (fWriter == find_thread(NULL)); +} + +// make_sem_name +static +void +make_sem_name(char *buffer, const char *name, const char *suffix) +{ + if (!name) + name = "unnamed_rwlocker"; + int32 nameLen = strlen(name); + int32 suffixLen = strlen(suffix); + if (suffixLen >= B_OS_NAME_LENGTH) + suffixLen = B_OS_NAME_LENGTH - 1; + if (nameLen + suffixLen >= B_OS_NAME_LENGTH) + nameLen = B_OS_NAME_LENGTH - suffixLen - 1; + memcpy(buffer, name, nameLen); + memcpy(buffer + nameLen, suffix, suffixLen); + buffer[nameLen + suffixLen] = '\0'; +} + +// _Init +status_t +RWLocker::_Init(const char* name) +{ + // init the data lock benaphore + fLock.semaphore = create_sem(0, name); + fLock.counter = 0; + // init the mutex benaphore + char semName[B_OS_NAME_LENGTH]; + make_sem_name(semName, name, "_mutex"); + fMutex.semaphore = create_sem(0, semName); + fMutex.counter = 0; + // init the queueing benaphore + make_sem_name(semName, name, "_queue"); + fQueue.semaphore = create_sem(0, semName); + fQueue.counter = 0; + return InitCheck(); +} + +// _ReadLock +// +// /timeout/ -- absolute timeout +status_t +RWLocker::_ReadLock(bigtime_t timeout) +{ + status_t error = B_OK; + thread_id thread = find_thread(NULL); + bool locked = false; + if (_AcquireBenaphore(fLock) == B_OK) { + // Check, if we already own a read (or write) lock. In this case we + // can skip the usual locking procedure. + if (thread == fWriter) { + // We already own a write lock. + fWriterReaderCount++; + locked = true; + } else if (ReadLockInfo* info = _ReadLockInfoAt(_IndexOf(thread))) { + // We already own a read lock. + info->count++; + fReaderCount++; + locked = true; + } + _ReleaseBenaphore(fLock); + } else // failed to lock the data + error = B_ERROR; + // Usual locking, i.e. we do not already own a read or write lock. + if (error == B_OK && !locked) { + error = _AcquireBenaphore(fQueue, timeout); + if (error == B_OK) { + if (_AcquireBenaphore(fLock) == B_OK) { + bool firstReader = false; + if (++fReaderCount == 1) { + // We are the first reader. + _NewReadLockInfo(thread); + firstReader = true; + } else + _NewReadLockInfo(thread); + _ReleaseBenaphore(fLock); + // The first reader needs to lock the mutex. + if (firstReader) { + error = _AcquireBenaphore(fMutex, timeout); + switch (error) { + case B_OK: + // fine + break; + case B_TIMED_OUT: { + // clean up + if (_AcquireBenaphore(fLock) == B_OK) { + _DeleteReadLockInfo(_IndexOf(thread)); + fReaderCount--; + _ReleaseBenaphore(fLock); + } + break; + } + default: + // Probably we are going to be destroyed. + break; + } + } + // Let the next candidate enter the game. + _ReleaseBenaphore(fQueue); + } else { + // We couldn't lock the data, which can only happen, if + // we're going to be destroyed. + error = B_ERROR; + } + } + } + return error; +} + +// _WriteLock +// +// /timeout/ -- absolute timeout +status_t +RWLocker::_WriteLock(bigtime_t timeout) +{ + status_t error = B_ERROR; + if (_AcquireBenaphore(fLock) == B_OK) { + bool infiniteTimeout = (timeout == B_INFINITE_TIMEOUT); + bool locked = false; + int32 readerCount = 0; + thread_id thread = find_thread(NULL); + int32 index = _IndexOf(thread); + if (ReadLockInfo* info = _ReadLockInfoAt(index)) { + // We already own a read lock. + if (fWriterCount > 0) { + // There are writers before us. + if (infiniteTimeout) { + // Timeout is infinite and there are writers before us. + // Unregister the read locks and lock as usual. + readerCount = info->count; + fWriterCount++; + fReaderCount -= readerCount; + _DeleteReadLockInfo(index); + error = B_OK; + } else { + // The timeout is finite and there are readers before us: + // let the write lock request fail. + error = B_WOULD_BLOCK; + } + } else if (info->count == fReaderCount) { + // No writers before us. + // We are the only read lock owners. Just move the read lock + // info data to the special writer fields and then we are done. + // Note: At this point we may overtake readers that already + // have acquired the queueing benaphore, but have not yet + // locked the data. But that doesn't harm. + fWriter = thread; + fWriterCount++; + fWriterWriterCount = 1; + fWriterReaderCount = info->count; + fReaderCount -= fWriterReaderCount; + _DeleteReadLockInfo(index); + locked = true; + error = B_OK; + } else { + // No writers before us, but other readers. + // Note, we're quite restrictive here. If there are only + // readers before us, we could reinstall our readers, if + // our request times out. Unfortunately it is not easy + // to ensure, that no writer overtakes us between unlocking + // the data and acquiring the queuing benaphore. + if (infiniteTimeout) { + // Unregister the readers and lock as usual. + readerCount = info->count; + fWriterCount++; + fReaderCount -= readerCount; + _DeleteReadLockInfo(index); + error = B_OK; + } else + error = B_WOULD_BLOCK; + } + } else { + // We don't own a read lock. + if (fWriter == thread) { + // ... but a write lock. + fWriterCount++; + fWriterWriterCount++; + locked = true; + error = B_OK; + } else { + // We own neither read nor write locks. + // Lock as usual. + fWriterCount++; + error = B_OK; + } + } + _ReleaseBenaphore(fLock); + // Usual locking... + // First step: acquire the queueing benaphore. + if (!locked && error == B_OK) { + error = _AcquireBenaphore(fQueue, timeout); + switch (error) { + case B_OK: + break; + case B_TIMED_OUT: { + // clean up + if (_AcquireBenaphore(fLock) == B_OK) { + fWriterCount--; + _ReleaseBenaphore(fLock); + } // else: failed to lock the data: we're probably going + // to die. + break; + } + default: + // Probably we're going to die. + break; + } + } + // Second step: acquire the mutex benaphore. + if (!locked && error == B_OK) { + error = _AcquireBenaphore(fMutex, timeout); + switch (error) { + case B_OK: { + // Yeah, we made it. Set the special writer fields. + fWriter = thread; + fWriterWriterCount = 1; + fWriterReaderCount = readerCount; + break; + } + case B_TIMED_OUT: { + // clean up + if (_AcquireBenaphore(fLock) == B_OK) { + fWriterCount--; + _ReleaseBenaphore(fLock); + } // else: failed to lock the data: we're probably going + // to die. + break; + } + default: + // Probably we're going to die. + break; + } + // Whatever happened, we have to release the queueing benaphore. + _ReleaseBenaphore(fQueue); + } + } else // failed to lock the data + error = B_ERROR; + return error; +} + +// _AddReadLockInfo +int32 +RWLocker::_AddReadLockInfo(ReadLockInfo* info) +{ + int32 index = fReadLockInfos.CountItems(); + fReadLockInfos.AddItem(info, index); + return index; +} + +// _NewReadLockInfo +// +// Create a new read lock info for the supplied thread and add it to the +// list. Returns the index of the info. +int32 +RWLocker::_NewReadLockInfo(thread_id thread, int32 count) +{ + ReadLockInfo* info = new(nothrow) ReadLockInfo; + info->reader = thread; + info->count = count; + return _AddReadLockInfo(info); +} + +// _DeleteReadLockInfo +void +RWLocker::_DeleteReadLockInfo(int32 index) +{ + if (ReadLockInfo* info = fReadLockInfos.ItemAt(index)) { + fReadLockInfos.RemoveItem(index); + delete info; + } +} + +// _ReadLockInfoAt +RWLocker::ReadLockInfo* +RWLocker::_ReadLockInfoAt(int32 index) const +{ + return fReadLockInfos.ItemAt(index); +} + +// _IndexOf +int32 +RWLocker::_IndexOf(thread_id thread) const +{ + int32 count = fReadLockInfos.CountItems(); + for (int32 i = 0; i < count; i++) { + if (_ReadLockInfoAt(i)->reader == thread) + return i; + } + return -1; +} + +// _AcquireBenaphore +status_t +RWLocker::_AcquireBenaphore(Benaphore& benaphore, bigtime_t timeout) +{ + status_t error = B_OK; + if (atomic_add(&benaphore.counter, 1) > 0) { + error = acquire_sem_etc(benaphore.semaphore, 1, B_ABSOLUTE_TIMEOUT, + timeout); + } + return error; +} + +// _ReleaseBenaphore +void +RWLocker::_ReleaseBenaphore(Benaphore& benaphore) +{ + if (atomic_add(&benaphore.counter, -1) > 1) + release_sem(benaphore.semaphore); +} + diff --git a/src/kernel/core/disk_device_manager/RWLocker.h b/src/kernel/core/disk_device_manager/RWLocker.h new file mode 100644 index 0000000000..b657c1b654 --- /dev/null +++ b/src/kernel/core/disk_device_manager/RWLocker.h @@ -0,0 +1,129 @@ +// RWLocker.h +// +// This class provides a reader/writer locking mechanism: +// * A writer needs an exclusive lock. +// * For a reader a non-exclusive lock to be shared with other readers is +// sufficient. +// * The ownership of a lock is bound to the thread that requested the lock; +// the same thread has to call Unlock() later. +// * Nested locking is supported: a number of XXXLock() calls needs to be +// bracketed by the same number of XXXUnlock() calls. +// * The lock acquiration strategy is fair: a lock applicant needs to wait +// only for those threads that already own a lock or requested one before +// the current thread. No one can overtake. E.g. if a thread owns a read +// lock, another one is waiting for a write lock, then a third one +// requesting a read lock has to wait until the write locker is done. +// This does not hold for threads that already own a lock (nested locking). +// A read lock owner is immediately granted another read lock and a write +// lock owner another write or a read lock. +// * A write lock owner is allowed to request a read lock and a read lock +// owner a write lock. While the first case is not problematic, the +// second one needs some further explanation: A read lock owner requesting +// a write lock temporarily looses its read lock(s) until the write lock +// is granted. Otherwise two read lock owning threads trying to get +// write locks at the same time would dead lock each other. The only +// problem with this solution is, that the write lock acquiration must +// not fail, because in that case the thread could not be given back +// its read lock(s), since another thread may have been given a write lock +// in the mean time. Fortunately locking can fail only either, if the +// locker has been deleted, or, if a timeout occured. Therefore +// WriteLockWithTimeout() immediatlely returns with a B_WOULD_BLOCK error +// code, if the caller already owns a read lock (but no write lock) and +// another thread already owns or has requested a read or write lock. +// * Calls to read and write locking methods may interleave arbitrarily, +// e.g.: ReadLock(); WriteLock(); ReadUnlock(); WriteUnlock(); +// +// Important note: Read/WriteLock() can fail only, if the locker has been +// deleted. However, it is NOT save to invoke any method on a deleted +// locker object. +// +// Implementation details: +// A locker needs three semaphores (a BLocker and two semaphores): one +// to protect the lockers data, one as a reader/writer mutex (to be +// acquired by each writer and the first reader) and one for queueing +// waiting readers and writers. The simplified locking/unlocking +// algorithm is the following: +// +// writer reader +// queue.acquire() queue.acquire() +// mutex.acquire() if (first reader) mutex.acquire() +// queue.release() queue.release() +// ... ... +// mutex.release() if (last reader) mutex.release() +// +// One thread at maximum waits at the mutex, the others at the queueing +// semaphore. Unfortunately features as nested locking and timeouts make +// things more difficult. Therefore readers as well as writers need to check +// whether they already own a lock before acquiring the queueing semaphore. +// The data for the readers are stored in a list of ReadLockInfo structures; +// the writer data are stored in some special fields. /fReaderCount/ and +// /fWriterCount/ contain the total count of unbalanced Read/WriteLock() +// calls, /fWriterReaderCount/ and /fWriterWriterCount/ only from those of +// the current write lock owner (/fWriter/). To be a bit more precise: +// /fWriterReaderCount/ is not contained in /fReaderCount/, but +// /fWriterWriterCount/ is contained in /fWriterCount/. Therefore +// /fReaderCount/ can be considered to be the count of true reader's read +// locks. + +#ifndef RW_LOCKER_H +#define RW_LOCKER_H + +#include + +#include "List.h" + +class RWLocker { + public: + RWLocker(); + RWLocker(const char* name); + virtual ~RWLocker(); + + status_t InitCheck() const; + + bool ReadLock(); + status_t ReadLockWithTimeout(bigtime_t timeout); + void ReadUnlock(); + bool IsReadLocked() const; + + bool WriteLock(); + status_t WriteLockWithTimeout(bigtime_t timeout); + void WriteUnlock(); + bool IsWriteLocked() const; + + private: + struct ReadLockInfo; + struct Benaphore { + sem_id semaphore; + int32 counter; + }; + + private: + status_t _Init(const char* name); + status_t _ReadLock(bigtime_t timeout); + status_t _WriteLock(bigtime_t timeout); + + int32 _AddReadLockInfo(ReadLockInfo* info); + int32 _NewReadLockInfo(thread_id thread, + int32 count = 1); + void _DeleteReadLockInfo(int32 index); + ReadLockInfo* _ReadLockInfoAt(int32 index) const; + int32 _IndexOf(thread_id thread) const; + + static status_t _AcquireBenaphore(Benaphore& benaphore, + bigtime_t timeout = B_INFINITE_TIMEOUT); + static void _ReleaseBenaphore(Benaphore& benaphore); + + private: + mutable Benaphore fLock; // data lock + Benaphore fMutex; // critical code mutex + Benaphore fQueue; // queueing semaphore + int32 fReaderCount; // total count... + int32 fWriterCount; // total count... + List fReadLockInfos; + thread_id fWriter; // current write lock owner + int32 fWriterWriterCount; // write lock owner count + int32 fWriterReaderCount; // writer read lock owner + // count +}; + +#endif // RW_LOCKER_H