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 23 const int32 LARGE_NUMBER = 100000; 24 25 26 MultiLocker::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 68 MultiLocker::~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 100 status_t 101 MultiLocker::InitCheck() 102 { 103 return fInit; 104 } 105 106 107 #if !DEBUG 108 // #pragma mark - Standard versions 109 110 111 bool 112 MultiLocker::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 133 bool 134 MultiLocker::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 152 bool 153 MultiLocker::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 171 bool 172 MultiLocker::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 190 bool 191 MultiLocker::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 213 bool 214 MultiLocker::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 235 bool 236 MultiLocker::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 265 bool 266 MultiLocker::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 300 bool 301 MultiLocker::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 323 bool 324 MultiLocker::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 350 bool 351 MultiLocker::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 383 void 384 MultiLocker::_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 395 void 396 MultiLocker::_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