⛏️ index : haiku.git

/*
 * Copyright 2006, Haiku.
 * Distributed under the terms of the MIT License.
 *
 * Authors:
 *		IngoWeinhold <bonefish@cs.tu-berlin.de>
 */

#include "RWLocker.h"

#include <String.h>

// 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(name),
	  fMutex(),
	  fQueue(),
	  fReaderCount(0),
	  fWriterCount(0),
	  fReadLockInfos(8),
	  fWriter(B_ERROR),
	  fWriterWriterCount(0),
	  fWriterReaderCount(0)
{
	_Init(name);
}

// destructor
RWLocker::~RWLocker()
{
	fLock.Lock();
	delete_sem(fMutex.semaphore);
	delete_sem(fQueue.semaphore);
	for (int32 i = 0; ReadLockInfo* info = _ReadLockInfoAt(i); i++)
		delete info;
}

// 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 (fLock.Lock()) {
		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
		}
		fLock.Unlock();
	}	// 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 (fLock.Lock()) {
		thread_id thread = find_thread(NULL);
		result = (thread == fWriter || _IndexOf(thread) >= 0);
		fLock.Unlock();
	}
	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 (fLock.Lock()) {
		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()
		fLock.Unlock();
	}	// 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));
}

// _Init
void
RWLocker::_Init(const char* name)
{
	// init the mutex benaphore
	BString mutexName(name);
	mutexName += "_RWLocker_mutex";
	fMutex.semaphore = create_sem(0, mutexName.String());
	fMutex.counter = 0;
	// init the queueing benaphore
	BString queueName(name);
	queueName += "_RWLocker_queue";
	fQueue.semaphore = create_sem(0, queueName.String());
	fQueue.counter = 0;
}

// _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 (fLock.Lock()) {
		// 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;
		}
		fLock.Unlock();
	} 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 (fLock.Lock()) {
				bool firstReader = false;
				if (++fReaderCount == 1) {
					// We are the first reader.
					_NewReadLockInfo(thread);
					firstReader = true;
				} else
					_NewReadLockInfo(thread);
				fLock.Unlock();
				// 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 (fLock.Lock()) {
								_DeleteReadLockInfo(_IndexOf(thread));
								fReaderCount--;
								fLock.Unlock();
							}
							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 (fLock.Lock()) {
		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;
			}
		}
		fLock.Unlock();
		// 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 (fLock.Lock()) {
						fWriterCount--;
						fLock.Unlock();
					}	// 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 (fLock.Lock()) {
						fWriterCount--;
						fLock.Unlock();
					}	// 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 ReadLockInfo;
	info->reader = thread;
	info->count = count;
	return _AddReadLockInfo(info);
}

// _DeleteReadLockInfo
void
RWLocker::_DeleteReadLockInfo(int32 index)
{
	if (ReadLockInfo* info = (ReadLockInfo*)fReadLockInfos.RemoveItem(index))
		delete info;
}

// _ReadLockInfoAt
RWLocker::ReadLockInfo*
RWLocker::_ReadLockInfoAt(int32 index) const
{
	return (ReadLockInfo*)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);
}