1 //------------------------------------------------------------------------------ 2 // Copyright (c) 2001-2002, OpenBeOS 3 // 4 // Permission is hereby granted, free of charge, to any person obtaining a 5 // copy of this software and associated documentation files (the "Software"), 6 // to deal in the Software without restriction, including without limitation 7 // the rights to use, copy, modify, merge, publish, distribute, sublicense, 8 // and/or sell copies of the Software, and to permit persons to whom the 9 // Software is furnished to do so, subject to the following conditions: 10 // 11 // The above copyright notice and this permission notice shall be included in 12 // all copies or substantial portions of the Software. 13 // 14 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 19 // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 20 // DEALINGS IN THE SOFTWARE. 21 // 22 // File Name: Locker.cpp 23 // Author(s): Erik Jaesler <erik@cgsoftware.com> 24 // 25 // Description: Semaphore-type class for thread safety 26 // 27 // 28 //------------------------------------------------------------------------------ 29 30 #include "Locker.h" 31 #include <OS.h> 32 #include <SupportDefs.h> 33 34 35 #ifdef USE_OPENBEOS_NAMESPACE 36 namespace OpenBeOS { 37 #endif 38 39 40 // 41 // Data Member Documentation: 42 // 43 // The "fBenaphoreCount" member is set to 1 if the BLocker style is 44 // semaphore. If the style is benaphore, it is initialized to 0 and 45 // is incremented atomically when it is acquired, decremented when it 46 // is released. By setting the benaphore count to 1 when the style is 47 // semaphore, the benaphore effectively becomes a semaphore. I was able 48 // to determine this is what Be's implementation does by testing the 49 // result of the CountLockRequests() member. 50 // 51 // The "fSemaphoreID" member holds the sem_id returned from create_sem() 52 // when the BLocker is constructed. It is used to acquire and release 53 // the lock regardless of the lock style (semaphore or benaphore). 54 // 55 // The "fLockOwner" member holds the thread_id of the thread which 56 // currently holds the lock. If no thread holds the lock, it is set to 57 // B_ERROR. 58 // 59 // The "fRecursiveCount" member holds a count of the number of times the 60 // thread holding the lock has acquired the lock without a matching unlock. 61 // It is basically the number of times the thread must call Unlock() before 62 // the lock can be acquired by a different thread. 63 // 64 65 66 // 67 // Constructors: 68 // 69 // All constructors just pass their arguments to InitLocker(). Note that 70 // the default for "name" is "some BLocker" and "benaphore_style" is true. 71 // 72 73 BLocker::BLocker() 74 { 75 InitLocker("some BLocker", true); 76 } 77 78 79 BLocker::BLocker(const char *name) 80 { 81 InitLocker(name, true); 82 } 83 84 85 BLocker::BLocker(bool benaphore_style) 86 { 87 InitLocker("some BLocker", benaphore_style); 88 } 89 90 91 BLocker::BLocker(const char *name, 92 bool benaphore_style) 93 { 94 InitLocker(name, benaphore_style); 95 } 96 97 98 // 99 // This constructor is not documented. The final argument is ignored for 100 // now. In Be's headers, its called "for_IPC". DO NOT USE THIS 101 // CONSTRUCTOR! 102 // 103 BLocker::BLocker(const char *name, 104 bool benaphore_style, 105 bool) 106 { 107 InitLocker(name, benaphore_style); 108 } 109 110 111 // 112 // The destructor just deletes the semaphore. By deleting the semaphore, 113 // any threads waiting to acquire the BLocker will be unblocked. 114 // 115 BLocker::~BLocker() 116 { 117 delete_sem(fSemaphoreID); 118 } 119 120 121 bool 122 BLocker::Lock(void) 123 { 124 status_t result; 125 126 return (AcquireLock(B_INFINITE_TIMEOUT, &result)); 127 } 128 129 130 status_t 131 BLocker::LockWithTimeout(bigtime_t timeout) 132 { 133 status_t result; 134 135 AcquireLock(timeout, &result); 136 return result; 137 } 138 139 140 141 void 142 BLocker::Unlock(void) 143 { 144 // If the thread currently holds the lockdecrement 145 if (IsLocked()) { 146 147 // Decrement the number of outstanding locks this thread holds 148 // on this BLocker. 149 fRecursiveCount--; 150 151 // If the recursive count is now at 0, that means the BLocker has 152 // been released by the thread. 153 if (fRecursiveCount == 0) { 154 155 // The BLocker is no longer owned by any thread. 156 fLockOwner = B_ERROR; 157 158 // Decrement the benaphore count and store the undecremented 159 // value in oldBenaphoreCount. 160 int32 oldBenaphoreCount = atomic_add(&fBenaphoreCount, -1); 161 162 // If the oldBenaphoreCount is greater than 1, then there is 163 // at lease one thread waiting for the lock in the case of a 164 // benaphore. 165 if (oldBenaphoreCount > 1) { 166 167 // Since there are threads waiting for the lock, it must 168 // be released. Note, the old benaphore count will always be 169 // greater than 1 for a semaphore so the release is always done. 170 release_sem(fSemaphoreID); 171 } 172 } 173 } 174 } 175 176 177 thread_id 178 BLocker::LockingThread(void) const 179 { 180 return fLockOwner; 181 } 182 183 184 bool 185 BLocker::IsLocked(void) const 186 { 187 // This member returns true if the calling thread holds the lock. 188 // The easiest way to determine this is to compare the result of 189 // find_thread() to the fLockOwner. 190 return (find_thread(NULL) == fLockOwner); 191 } 192 193 194 int32 195 BLocker::CountLocks(void) const 196 { 197 return fRecursiveCount; 198 } 199 200 201 int32 202 BLocker::CountLockRequests(void) const 203 { 204 return fBenaphoreCount; 205 } 206 207 208 sem_id 209 BLocker::Sem(void) const 210 { 211 return fSemaphoreID; 212 } 213 214 215 void 216 BLocker::InitLocker(const char *name, 217 bool benaphore) 218 { 219 if (benaphore) { 220 // Because this is a benaphore, initialize the benaphore count and 221 // create the semaphore. Because this is a benaphore, the semaphore 222 // count starts at 0 (ie acquired). 223 fBenaphoreCount = 0; 224 fSemaphoreID = create_sem(0, name); 225 } else { 226 // Because this is a semaphore, initialize the benaphore count to -1 227 // and create the semaphore. Because this is semaphore style, the 228 // semaphore count starts at 1 so that one thread can acquire it and 229 // the next thread to acquire it will block. 230 fBenaphoreCount = 1; 231 fSemaphoreID = create_sem(1, name); 232 } 233 234 // The lock is currently not acquired so there is no owner. 235 fLockOwner = B_ERROR; 236 237 // The lock is currently not acquired so the recursive count is zero. 238 fRecursiveCount = 0; 239 } 240 241 242 bool 243 BLocker::AcquireLock(bigtime_t timeout, 244 status_t *error) 245 { 246 // By default, return no error. 247 *error = B_NO_ERROR; 248 249 // Only try to acquire the lock if the thread doesn't already own it. 250 if (!IsLocked()) { 251 252 // Increment the benaphore count and test to see if it was already greater 253 // than 0. If it is greater than 0, then some thread already has the 254 // benaphore or the style is a semaphore. Either way, we need to acquire 255 // the semaphore in this case. 256 int32 oldBenaphoreCount = atomic_add(&fBenaphoreCount, 1); 257 if (oldBenaphoreCount > 0) { 258 259 *error = acquire_sem_etc(fSemaphoreID, 1, B_RELATIVE_TIMEOUT, 260 timeout); 261 // Note, if the lock here does time out, the benaphore count 262 // is not decremented. By doing this, the benaphore count will 263 // never go back to zero. This means that the locking essentially 264 // changes to semaphore style if this was a benaphore. 265 // 266 // Doing the decrement of the benaphore count when the acquisition 267 // fails is a risky thing to do. If you decrement the counter at 268 // the same time the thread which holds the benaphore does an 269 // Unlock(), there is serious risk of a race condition. 270 // 271 // If the Unlock() sees a positive count and releases the semaphore 272 // and then the timed out thread decrements the count to 0, there 273 // is no one to take the semaphore. The next two threads will be 274 // able to acquire the benaphore at the same time! The first will 275 // increment the counter and acquire the lock. The second will 276 // acquire the semaphore and therefore the lock. Not good. 277 // 278 // This has been discussed on the becodetalk mailing list and 279 // Trey from Be had this to say: 280 // 281 // I looked at the LockWithTimeout() code, and it does not have 282 // _this_ (ie the race condition) problem. It circumvents it by 283 // NOT doing the atomic_add(&count, -1) if the semaphore 284 // acquisition fails. This means that if a 285 // BLocker::LockWithTimeout() times out, all other Lock*() attempts 286 // turn into guaranteed semaphore grabs, _with_ the overhead of a 287 // (now) useless atomic_add(). 288 // 289 // Given Trey's comments, it looks like Be took the same approach 290 // I did. The output of CountLockRequests() of Be's implementation 291 // confirms Trey's comments also. 292 // 293 // Finally some thoughts for the future with this code: 294 // - If 2^31 timeouts occur on a 32-bit machine (ie today), 295 // the benaphore count will wrap to a negative number. This 296 // would have unknown consequences on the ability of the BLocker 297 // to continue to function. 298 // 299 } 300 } 301 302 // If the lock has successfully been acquired. 303 if (*error == B_NO_ERROR) { 304 305 // Set the lock owner to this thread and increment the recursive count 306 // by one. The recursive count is incremented because one more Unlock() 307 // is now required to release the lock (ie, 0 => 1, 1 => 2 etc). 308 fLockOwner = find_thread(NULL); 309 fRecursiveCount++; 310 } 311 312 // Return true if the lock has been acquired. 313 return (*error == B_NO_ERROR); 314 } 315 316 317 #ifdef USE_OPENBEOS_NAMESPACE 318 } 319 #endif 320