/* * 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 #include #include #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