haiku/src/servers/app/MultiLocker.cpp

405 lines
7.8 KiB
C++

/*
* Copyright 2005-2009, Haiku, Inc. All Rights Reserved.
* Distributed under the terms of the MIT license.
*
* Copyright 1999, Be Incorporated. All Rights Reserved.
* This file may be used under the terms of the Be Sample Code License.
*/
#include "MultiLocker.h"
#include <Debug.h>
#include <Errors.h>
#include <OS.h>
#define TIMING MULTI_LOCKER_TIMING
#ifndef DEBUG
# define DEBUG MULTI_LOCKER_DEBUG
#endif
const int32 LARGE_NUMBER = 100000;
MultiLocker::MultiLocker(const char* baseName)
:
#if DEBUG
fDebugArray(NULL),
fMaxThreads(0),
fWriterNest(0),
fWriterThread(-1),
#endif
fInit(B_NO_INIT)
{
#if !DEBUG
rw_lock_init_etc(&fLock, baseName != NULL ? baseName : "some MultiLocker",
baseName != NULL ? RW_LOCK_FLAG_CLONE_NAME : 0);
fInit = B_OK;
#else
// we are in debug mode!
fLock = create_sem(LARGE_NUMBER, baseName != NULL ? baseName : "MultiLocker");
if (fLock >= 0)
fInit = B_OK;
// create the reader tracking list
// the array needs to be large enough to hold all possible threads
system_info sys;
get_system_info(&sys);
fMaxThreads = sys.max_threads;
fDebugArray = (int32 *) malloc(fMaxThreads * sizeof(int32));
for (int32 i = 0; i < fMaxThreads; i++) {
fDebugArray[i] = 0;
}
#endif
#if TIMING
//initialize the counter variables
rl_count = ru_count = wl_count = wu_count = islock_count = 0;
rl_time = ru_time = wl_time = wu_time = islock_time = 0;
#if DEBUG
reg_count = unreg_count = 0;
reg_time = unreg_time = 0;
#endif
#endif
}
MultiLocker::~MultiLocker()
{
// become the writer
if (!IsWriteLocked())
WriteLock();
// set locker to be uninitialized
fInit = B_NO_INIT;
#if !DEBUG
rw_lock_destroy(&fLock);
#else
delete_sem(fLock);
free(fDebugArray);
#endif
#if TIMING
// let's produce some performance numbers
printf("MultiLocker Statistics:\n"
"Avg ReadLock: %lld\n"
"Avg ReadUnlock: %lld\n"
"Avg WriteLock: %lld\n"
"Avg WriteUnlock: %lld\n"
"Avg IsWriteLocked: %lld\n",
rl_count > 0 ? rl_time / rl_count : 0,
ru_count > 0 ? ru_time / ru_count : 0,
wl_count > 0 ? wl_time / wl_count : 0,
wu_count > 0 ? wu_time / wu_count : 0,
islock_count > 0 ? islock_time / islock_count : 0);
#endif
}
status_t
MultiLocker::InitCheck()
{
return fInit;
}
#if !DEBUG
// #pragma mark - Standard versions
bool
MultiLocker::IsWriteLocked() const
{
#if TIMING
bigtime_t start = system_time();
#endif
bool writeLockHolder = false;
if (fInit == B_OK)
writeLockHolder = (find_thread(NULL) == fLock.holder);
#if TIMING
bigtime_t end = system_time();
islock_time += (end - start);
islock_count++;
#endif
return writeLockHolder;
}
bool
MultiLocker::ReadLock()
{
#if TIMING
bigtime_t start = system_time();
#endif
bool locked = (rw_lock_read_lock(&fLock) == B_OK);
#if TIMING
bigtime_t end = system_time();
rl_time += (end - start);
rl_count++;
#endif
return locked;
}
bool
MultiLocker::WriteLock()
{
#if TIMING
bigtime_t start = system_time();
#endif
bool locked = (rw_lock_write_lock(&fLock) == B_OK);
#if TIMING
bigtime_t end = system_time();
wl_time += (end - start);
wl_count++;
#endif
return locked;
}
bool
MultiLocker::ReadUnlock()
{
#if TIMING
bigtime_t start = system_time();
#endif
bool unlocked = (rw_lock_read_unlock(&fLock) == B_OK);
#if TIMING
bigtime_t end = system_time();
ru_time += (end - start);
ru_count++;
#endif
return unlocked;
}
bool
MultiLocker::WriteUnlock()
{
#if TIMING
bigtime_t start = system_time();
#endif
bool unlocked = (rw_lock_write_unlock(&fLock) == B_OK);
#if TIMING
bigtime_t end = system_time();
wu_time += (end - start);
wu_count++;
#endif
return unlocked;
}
#else // DEBUG
// #pragma mark - Debug versions
bool
MultiLocker::IsWriteLocked() const
{
#if TIMING
bigtime_t start = system_time();
#endif
bool writeLockHolder = false;
if (fInit == B_OK)
writeLockHolder = (find_thread(NULL) == fWriterThread);
#if TIMING
bigtime_t end = system_time();
islock_time += (end - start);
islock_count++;
#endif
return writeLockHolder;
}
bool
MultiLocker::ReadLock()
{
bool locked = false;
if (fInit != B_OK)
debugger("lock not initialized");
if (IsWriteLocked()) {
if (fWriterNest < 0)
debugger("ReadLock() - negative writer nest count");
fWriterNest++;
locked = true;
} else {
status_t status;
do {
status = acquire_sem(fLock);
} while (status == B_INTERRUPTED);
locked = status == B_OK;
if (locked)
_RegisterThread();
}
return locked;
}
bool
MultiLocker::WriteLock()
{
bool locked = false;
if (fInit != B_OK)
debugger("lock not initialized");
if (IsWriteLocked()) {
if (fWriterNest < 0)
debugger("WriteLock() - negative writer nest count");
fWriterNest++;
locked = true;
} else {
// new writer acquiring the lock
if (IsReadLocked())
debugger("Reader wants to become writer!");
status_t status;
do {
status = acquire_sem_etc(fLock, LARGE_NUMBER, 0, 0);
} while (status == B_INTERRUPTED);
locked = status == B_OK;
if (locked) {
// record thread information
fWriterThread = find_thread(NULL);
}
}
return locked;
}
bool
MultiLocker::ReadUnlock()
{
bool unlocked = false;
if (IsWriteLocked()) {
// writers simply decrement the nesting count
fWriterNest--;
if (fWriterNest < 0)
debugger("ReadUnlock() - negative writer nest count");
unlocked = true;
} else {
// decrement and retrieve the read counter
unlocked = release_sem_etc(fLock, 1, B_DO_NOT_RESCHEDULE) == B_OK;
if (unlocked)
_UnregisterThread();
}
return unlocked;
}
bool
MultiLocker::WriteUnlock()
{
bool unlocked = false;
if (IsWriteLocked()) {
// if this is a nested lock simply decrement the nest count
if (fWriterNest > 0) {
fWriterNest--;
unlocked = true;
} else {
// clear the information while still holding the lock
fWriterThread = -1;
unlocked = release_sem_etc(fLock, LARGE_NUMBER,
B_DO_NOT_RESCHEDULE) == B_OK;
}
} else {
char message[256];
snprintf(message, sizeof(message), "Non-writer attempting to "
"WriteUnlock() - write holder: %" B_PRId32, fWriterThread);
debugger(message);
}
return unlocked;
}
bool
MultiLocker::IsReadLocked() const
{
if (fInit == B_NO_INIT)
return false;
// determine if the lock is actually held
thread_id thread = find_thread(NULL);
return fDebugArray[thread % fMaxThreads] > 0;
}
/* these two functions manage the debug array for readers */
/* an array is created in the constructor large enough to hold */
/* an int32 for each of the maximum number of threads the system */
/* can have at one time. */
/* this array does not need to be locked because each running thread */
/* can be uniquely mapped to a slot in the array by performing: */
/* thread_id % max_threads */
/* each time ReadLock is called while in debug mode the thread_id */
/* is retrived in register_thread() and the count is adjusted in the */
/* array. If register thread is ever called and the count is not 0 then */
/* an illegal, potentially deadlocking nested ReadLock occured */
/* unregister_thread clears the appropriate slot in the array */
/* this system could be expanded or retracted to include multiple arrays of information */
/* in all fairness for it's current use, fDebugArray could be an array of bools */
/* The disadvantage of this system for maintaining state is that it sucks up a ton of */
/* memory. The other method (which would be slower), would involve an additional lock and */
/* traversing a list of cached information. As this is only for a debug mode, the extra memory */
/* was not deemed to be a problem */
void
MultiLocker::_RegisterThread()
{
thread_id thread = find_thread(NULL);
if (fDebugArray[thread % fMaxThreads] != 0)
debugger("Nested ReadLock!");
fDebugArray[thread % fMaxThreads]++;
}
void
MultiLocker::_UnregisterThread()
{
thread_id thread = find_thread(NULL);
ASSERT(fDebugArray[thread % fMaxThreads] == 1);
fDebugArray[thread % fMaxThreads]--;
}
#endif // DEBUG