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 DEBUG 1
MultiLocker::MultiLocker(const char* semaphoreBaseName)
: fInit(B_NO_INIT),
fReadCount(0),
fReadSem(-1),
fWriteCount(0),
fWriteSem(-1),
fLockCount(0),
fWriterLock(-1),
fWriterNest(0),
fWriterThread(-1),
fWriterStackBase(0),
fDebugArray(NULL),
fMaxThreads(0)
{
if (semaphoreBaseName) {
char name[128];
sprintf(name, "%s-%s", semaphoreBaseName, "ReadSem");
fReadSem = create_sem(0, name);
sprintf(name, "%s-%s", semaphoreBaseName, "WriteSem");
fWriteSem = create_sem(0, name);
sprintf(name, "%s-%s", semaphoreBaseName, "WriterLock");
fWriterLock = create_sem(0, name);
} else {
fReadSem = create_sem(0, "MultiLocker_ReadSem");
fWriteSem = create_sem(0, "MultiLocker_WriteSem");
fWriterLock = create_sem(0, "MultiLocker_WriterLock");
}
if (fReadSem >= 0 && fWriteSem >=0 && fWriterLock >= 0)
fInit = B_OK;
#if DEBUG
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
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()
{
if (!IsWriteLocked()) WriteLock();
fInit = B_NO_INIT;
delete_sem(fReadSem);
delete_sem(fWriteSem);
delete_sem(fWriterLock);
#if DEBUG
free(fDebugArray);
#endif
#if TIMING
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
);
#if DEBUG
printf( "Avg register_thread: %lld\n"
"Avg unregister_thread: %lld\n",
reg_count > 0 ? reg_time / reg_count : 0,
unreg_count > 0 ? unreg_time / unreg_count : 0
);
#endif
#endif
}
status_t
MultiLocker::InitCheck()
{
return fInit;
}
bool
MultiLocker::ReadLock()
{
#if TIMING
bigtime_t start = system_time();
#endif
bool locked = false;
if (fInit == B_OK) {
if (IsWriteLocked()) {
fWriterNest++;
locked = true;
} else {
int32 current_count = atomic_add(&fReadCount, 1);
if (current_count < 0) {
locked = (acquire_sem_etc(fReadSem, 1, B_DO_NOT_RESCHEDULE,
B_INFINITE_TIMEOUT) == B_OK);
} else locked = true;
#if DEBUG
if (locked) register_thread();
#endif
}
}
#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 = false;
if (fInit == B_OK) {
uint32 stack_base = 0;
thread_id thread = -1;
if (IsWriteLocked(&stack_base, &thread)) {
fWriterNest++;
locked = true;
} else {
if (atomic_add(&fLockCount, 1) >= 1) {
locked = (acquire_sem_etc(fWriterLock, 1, B_DO_NOT_RESCHEDULE,
B_INFINITE_TIMEOUT) == B_OK);
} else locked = true;
if (locked) {
int32 readers = atomic_add(&fReadCount, -LARGE_NUMBER);
if (readers > 0) {
locked = (acquire_sem_etc(fWriteSem, readers, B_DO_NOT_RESCHEDULE,
B_INFINITE_TIMEOUT) == B_OK);
}
if (locked) {
ASSERT(fWriterThread == -1);
fWriterThread = thread;
fWriterStackBase = stack_base;
}
}
}
}
#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 = false;
if (IsWriteLocked()) {
fWriterNest--;
unlocked = true;
} else {
int32 current_count = atomic_add(&fReadCount, -1);
if (current_count < 0) {
unlocked = (release_sem_etc(fWriteSem, 1,
B_DO_NOT_RESCHEDULE) == B_OK);
} else unlocked = true;
#ifdef DEBUG
if (unlocked) unregister_thread();
#endif
}
#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 = false;
if (IsWriteLocked()) {
if (fWriterNest > 0) {
fWriterNest--;
unlocked = true;
} else {
int32 readersWaiting = atomic_add(&fReadCount, LARGE_NUMBER) + LARGE_NUMBER;
if (readersWaiting > 0) {
unlocked = (release_sem_etc(fReadSem, readersWaiting,
B_DO_NOT_RESCHEDULE) == B_OK);
} else unlocked = true;
if (unlocked) {
fWriterThread = -1;
fWriterStackBase = 0;
if (atomic_add(&fLockCount, -1) > 1) {
unlocked = (release_sem_etc(fWriterLock, 1,
B_DO_NOT_RESCHEDULE) == B_OK);
}
}
}
} else debugger("Non-writer attempting to WriteUnlock()\n");
#if TIMING
bigtime_t end = system_time();
wu_time += (end - start);
wu_count++;
#endif
return unlocked;
}
bool
MultiLocker::IsWriteLocked(uint32 *the_stack_base, thread_id *the_thread)
{
#if TIMING
bigtime_t start = system_time();
#endif
bool write_lock_holder = false;
if (fInit == B_OK) {
uint32 stack_base;
thread_id thread = 0;
stack_base = (uint32) &write_lock_holder/B_PAGE_SIZE;
if (fWriterStackBase == stack_base) {
write_lock_holder = true;
} else {
thread = find_thread(NULL);
if (fWriterThread == thread) {
write_lock_holder = true;
}
}
if (the_stack_base != NULL) {
*the_stack_base = stack_base;
}
if (the_thread != NULL) {
*the_thread = thread;
}
}
#if TIMING
bigtime_t end = system_time();
islock_time += (end - start);
islock_count++;
#endif
return write_lock_holder;
}
bool
MultiLocker::IsReadLocked()
{
bool locked = true;
if (fInit == B_NO_INIT) locked = false;
#if DEBUG
thread_id thread = find_thread(NULL);
if (fDebugArray[thread % fMaxThreads] > 0) locked = true;
else locked = false;
#endif
return locked;
}
void
MultiLocker::register_thread()
{
#ifdef DEBUG
#if TIMING
bigtime_t start = system_time();
#endif
thread_id thread = find_thread(NULL);
ASSERT_WITH_MESSAGE(fDebugArray[thread%fMaxThreads] == 0,"Nested ReadLock!\n");
fDebugArray[thread%fMaxThreads]++;
#if TIMING
bigtime_t end = system_time();
reg_time += (end - start);
reg_count++;
#endif
#else
debugger("register_thread should never be called unless in DEBUG mode!\n");
#endif
}
void
MultiLocker::unregister_thread()
{
#ifdef DEBUG
#if TIMING
bigtime_t start = system_time();
#endif
thread_id thread = find_thread(NULL);
ASSERT(fDebugArray[thread%fMaxThreads] == 1);
fDebugArray[thread%fMaxThreads]--;
#if TIMING
bigtime_t end = system_time();
unreg_time += (end - start);
unreg_count++;
#endif
#else
debugger("unregister_thread should never be called unless in DEBUG mode!\n");
#endif
}