⛏️ index : haiku.git

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