1/* 2 * Copyright 2005-2009, Haiku, Inc. All Rights Reserved. 3 * Distributed under the terms of the MIT license. 4 * 5 * Copyright 1999, Be Incorporated. All Rights Reserved. 6 * This file may be used under the terms of the Be Sample Code License. 7 */ 8 9 10#include "MultiLocker.h" 11 12#include <Debug.h> 13#include <Errors.h> 14#include <OS.h> 15 16 17#define TIMING MULTI_LOCKER_TIMING 18#ifndef DEBUG 19# define DEBUG MULTI_LOCKER_DEBUG 20#endif 21 22 23const int32 LARGE_NUMBER = 100000; 24 25 26MultiLocker::MultiLocker(const char* baseName) 27 : 28#if DEBUG 29 fDebugArray(NULL), 30 fMaxThreads(0), 31 fWriterNest(0), 32 fWriterThread(-1), 33#endif 34 fInit(B_NO_INIT) 35{ 36#if !DEBUG 37 rw_lock_init_etc(&fLock, baseName != NULL ? baseName : "some MultiLocker", 38 baseName != NULL ? RW_LOCK_FLAG_CLONE_NAME : 0); 39 fInit = B_OK; 40#else 41 // we are in debug mode! 42 fLock = create_sem(LARGE_NUMBER, baseName != NULL ? baseName : "MultiLocker"); 43 if (fLock >= 0) 44 fInit = B_OK; 45 46 // create the reader tracking list 47 // the array needs to be large enough to hold all possible threads 48 system_info sys; 49 get_system_info(&sys); 50 fMaxThreads = sys.max_threads; 51 fDebugArray = (int32 *) malloc(fMaxThreads * sizeof(int32)); 52 for (int32 i = 0; i < fMaxThreads; i++) { 53 fDebugArray[i] = 0; 54 } 55#endif 56#if TIMING 57 //initialize the counter variables 58 rl_count = ru_count = wl_count = wu_count = islock_count = 0; 59 rl_time = ru_time = wl_time = wu_time = islock_time = 0; 60 #if DEBUG 61 reg_count = unreg_count = 0; 62 reg_time = unreg_time = 0; 63 #endif 64#endif 65} 66 67 68MultiLocker::~MultiLocker() 69{ 70 // become the writer 71 if (!IsWriteLocked()) 72 WriteLock(); 73 74 // set locker to be uninitialized 75 fInit = B_NO_INIT; 76 77#if !DEBUG 78 rw_lock_destroy(&fLock); 79#else 80 delete_sem(fLock); 81 free(fDebugArray); 82#endif 83#if TIMING 84 // let's produce some performance numbers 85 printf("MultiLocker Statistics:\n" 86 "Avg ReadLock: %lld\n" 87 "Avg ReadUnlock: %lld\n" 88 "Avg WriteLock: %lld\n" 89 "Avg WriteUnlock: %lld\n" 90 "Avg IsWriteLocked: %lld\n", 91 rl_count > 0 ? rl_time / rl_count : 0, 92 ru_count > 0 ? ru_time / ru_count : 0, 93 wl_count > 0 ? wl_time / wl_count : 0, 94 wu_count > 0 ? wu_time / wu_count : 0, 95 islock_count > 0 ? islock_time / islock_count : 0); 96#endif 97} 98 99 100status_t 101MultiLocker::InitCheck() 102{ 103 return fInit; 104} 105 106 107#if !DEBUG 108// #pragma mark - Standard versions 109 110 111bool 112MultiLocker::IsWriteLocked() const 113{ 114#if TIMING 115 bigtime_t start = system_time(); 116#endif 117 118 bool writeLockHolder = false; 119 120 if (fInit == B_OK) 121 writeLockHolder = (find_thread(NULL) == fLock.holder); 122 123#if TIMING 124 bigtime_t end = system_time(); 125 islock_time += (end - start); 126 islock_count++; 127#endif 128 129 return writeLockHolder; 130} 131 132 133bool 134MultiLocker::ReadLock() 135{ 136#if TIMING 137 bigtime_t start = system_time(); 138#endif 139 140 bool locked = (rw_lock_read_lock(&fLock) == B_OK); 141 142#if TIMING 143 bigtime_t end = system_time(); 144 rl_time += (end - start); 145 rl_count++; 146#endif 147 148 return locked; 149} 150 151 152bool 153MultiLocker::WriteLock() 154{ 155#if TIMING 156 bigtime_t start = system_time(); 157#endif 158 159 bool locked = (rw_lock_write_lock(&fLock) == B_OK); 160 161#if TIMING 162 bigtime_t end = system_time(); 163 wl_time += (end - start); 164 wl_count++; 165#endif 166 167 return locked; 168} 169 170 171bool 172MultiLocker::ReadUnlock() 173{ 174#if TIMING 175 bigtime_t start = system_time(); 176#endif 177 178 bool unlocked = (rw_lock_read_unlock(&fLock) == B_OK); 179 180#if TIMING 181 bigtime_t end = system_time(); 182 ru_time += (end - start); 183 ru_count++; 184#endif 185 186 return unlocked; 187} 188 189 190bool 191MultiLocker::WriteUnlock() 192{ 193#if TIMING 194 bigtime_t start = system_time(); 195#endif 196 197 bool unlocked = (rw_lock_write_unlock(&fLock) == B_OK); 198 199#if TIMING 200 bigtime_t end = system_time(); 201 wu_time += (end - start); 202 wu_count++; 203#endif 204 205 return unlocked; 206} 207 208 209#else // DEBUG 210// #pragma mark - Debug versions 211 212 213bool 214MultiLocker::IsWriteLocked() const 215{ 216#if TIMING 217 bigtime_t start = system_time(); 218#endif 219 220 bool writeLockHolder = false; 221 222 if (fInit == B_OK) 223 writeLockHolder = (find_thread(NULL) == fWriterThread); 224 225#if TIMING 226 bigtime_t end = system_time(); 227 islock_time += (end - start); 228 islock_count++; 229#endif 230 231 return writeLockHolder; 232} 233 234 235bool 236MultiLocker::ReadLock() 237{ 238 bool locked = false; 239 240 if (fInit != B_OK) 241 debugger("lock not initialized"); 242 243 if (IsWriteLocked()) { 244 if (fWriterNest < 0) 245 debugger("ReadLock() - negative writer nest count"); 246 247 fWriterNest++; 248 locked = true; 249 } else { 250 status_t status; 251 do { 252 status = acquire_sem(fLock); 253 } while (status == B_INTERRUPTED); 254 255 locked = status == B_OK; 256 257 if (locked) 258 _RegisterThread(); 259 } 260 261 return locked; 262} 263 264 265bool 266MultiLocker::WriteLock() 267{ 268 bool locked = false; 269 270 if (fInit != B_OK) 271 debugger("lock not initialized"); 272 273 if (IsWriteLocked()) { 274 if (fWriterNest < 0) 275 debugger("WriteLock() - negative writer nest count"); 276 277 fWriterNest++; 278 locked = true; 279 } else { 280 // new writer acquiring the lock 281 if (IsReadLocked()) 282 debugger("Reader wants to become writer!"); 283 284 status_t status; 285 do { 286 status = acquire_sem_etc(fLock, LARGE_NUMBER, 0, 0); 287 } while (status == B_INTERRUPTED); 288 289 locked = status == B_OK; 290 if (locked) { 291 // record thread information 292 fWriterThread = find_thread(NULL); 293 } 294 } 295 296 return locked; 297} 298 299 300bool 301MultiLocker::ReadUnlock() 302{ 303 bool unlocked = false; 304 305 if (IsWriteLocked()) { 306 // writers simply decrement the nesting count 307 fWriterNest--; 308 if (fWriterNest < 0) 309 debugger("ReadUnlock() - negative writer nest count"); 310 311 unlocked = true; 312 } else { 313 // decrement and retrieve the read counter 314 unlocked = release_sem_etc(fLock, 1, B_DO_NOT_RESCHEDULE) == B_OK; 315 if (unlocked) 316 _UnregisterThread(); 317 } 318 319 return unlocked; 320} 321 322 323bool 324MultiLocker::WriteUnlock() 325{ 326 bool unlocked = false; 327 328 if (IsWriteLocked()) { 329 // if this is a nested lock simply decrement the nest count 330 if (fWriterNest > 0) { 331 fWriterNest--; 332 unlocked = true; 333 } else { 334 // clear the information while still holding the lock 335 fWriterThread = -1; 336 unlocked = release_sem_etc(fLock, LARGE_NUMBER, 337 B_DO_NOT_RESCHEDULE) == B_OK; 338 } 339 } else { 340 char message[256]; 341 snprintf(message, sizeof(message), "Non-writer attempting to " 342 "WriteUnlock() - write holder: %" B_PRId32, fWriterThread); 343 debugger(message); 344 } 345 346 return unlocked; 347} 348 349 350bool 351MultiLocker::IsReadLocked() const 352{ 353 if (fInit == B_NO_INIT) 354 return false; 355 356 // determine if the lock is actually held 357 thread_id thread = find_thread(NULL); 358 return fDebugArray[thread % fMaxThreads] > 0; 359} 360 361 362/* these two functions manage the debug array for readers */ 363/* an array is created in the constructor large enough to hold */ 364/* an int32 for each of the maximum number of threads the system */ 365/* can have at one time. */ 366/* this array does not need to be locked because each running thread */ 367/* can be uniquely mapped to a slot in the array by performing: */ 368/* thread_id % max_threads */ 369/* each time ReadLock is called while in debug mode the thread_id */ 370/* is retrived in register_thread() and the count is adjusted in the */ 371/* array. If register thread is ever called and the count is not 0 then */ 372/* an illegal, potentially deadlocking nested ReadLock occured */ 373/* unregister_thread clears the appropriate slot in the array */ 374 375/* this system could be expanded or retracted to include multiple arrays of information */ 376/* in all fairness for it's current use, fDebugArray could be an array of bools */ 377 378/* The disadvantage of this system for maintaining state is that it sucks up a ton of */ 379/* memory. The other method (which would be slower), would involve an additional lock and */ 380/* traversing a list of cached information. As this is only for a debug mode, the extra memory */ 381/* was not deemed to be a problem */ 382 383void 384MultiLocker::_RegisterThread() 385{ 386 thread_id thread = find_thread(NULL); 387 388 if (fDebugArray[thread % fMaxThreads] != 0) 389 debugger("Nested ReadLock!"); 390 391 fDebugArray[thread % fMaxThreads]++; 392} 393 394 395void 396MultiLocker::_UnregisterThread() 397{ 398 thread_id thread = find_thread(NULL); 399 400 ASSERT(fDebugArray[thread % fMaxThreads] == 1); 401 fDebugArray[thread % fMaxThreads]--; 402} 403 404#endif // DEBUG 405