w95cv.c (10796B)
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 2 /* This Source Code Form is subject to the terms of the Mozilla Public 3 * License, v. 2.0. If a copy of the MPL was not distributed with this 4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 5 6 /* 7 * w95cv.c -- Windows 95 Machine-Dependent Code for Condition Variables 8 * 9 * We implement our own condition variable wait queue. Each thread 10 * has a semaphore object (thread->md.blocked_sema) to block on while 11 * waiting on a condition variable. 12 * 13 * We use a deferred condition notify algorithm. When PR_NotifyCondVar 14 * or PR_NotifyAllCondVar is called, the condition notifies are simply 15 * recorded in the _MDLock structure. We defer the condition notifies 16 * until right after we unlock the lock. This way the awakened threads 17 * have a better chance to reaquire the lock. 18 */ 19 20 #include "primpl.h" 21 22 /* 23 * AddThreadToCVWaitQueueInternal -- 24 * 25 * Add the thread to the end of the condition variable's wait queue. 26 * The CV's lock must be locked when this function is called. 27 */ 28 29 static void AddThreadToCVWaitQueueInternal(PRThread* thred, 30 struct _MDCVar* cv) { 31 PR_ASSERT((cv->waitTail != NULL && cv->waitHead != NULL) || 32 (cv->waitTail == NULL && cv->waitHead == NULL)); 33 cv->nwait += 1; 34 thred->md.inCVWaitQueue = PR_TRUE; 35 thred->md.next = NULL; 36 thred->md.prev = cv->waitTail; 37 if (cv->waitHead == NULL) { 38 cv->waitHead = thred; 39 } else { 40 cv->waitTail->md.next = thred; 41 } 42 cv->waitTail = thred; 43 } 44 45 /* 46 * md_UnlockAndPostNotifies -- 47 * 48 * Unlock the lock, and then do the deferred condition notifies. 49 * If waitThred and waitCV are not NULL, waitThred is added to 50 * the wait queue of waitCV before the lock is unlocked. 51 * 52 * This function is called by _PR_MD_WAIT_CV and _PR_MD_UNLOCK, 53 * the two places where a lock is unlocked. 54 */ 55 static void md_UnlockAndPostNotifies(_MDLock* lock, PRThread* waitThred, 56 _MDCVar* waitCV) { 57 PRIntn index; 58 _MDNotified post; 59 _MDNotified *notified, *prev = NULL; 60 61 /* 62 * Time to actually notify any conditions that were affected 63 * while the lock was held. Get a copy of the list that's in 64 * the lock structure and then zero the original. If it's 65 * linked to other such structures, we own that storage. 66 */ 67 post = lock->notified; /* a safe copy; we own the lock */ 68 69 #if defined(DEBUG) 70 ZeroMemory(&lock->notified, sizeof(_MDNotified)); /* reset */ 71 #else 72 lock->notified.length = 0; /* these are really sufficient */ 73 lock->notified.link = NULL; 74 #endif 75 76 /* 77 * Figure out how many threads we need to wake up. 78 */ 79 notified = &post; /* this is where we start */ 80 do { 81 for (index = 0; index < notified->length; ++index) { 82 _MDCVar* cv = notified->cv[index].cv; 83 PRThread* thred; 84 int i; 85 86 /* Fast special case: no waiting threads */ 87 if (cv->waitHead == NULL) { 88 notified->cv[index].notifyHead = NULL; 89 continue; 90 } 91 92 /* General case */ 93 if (-1 == notified->cv[index].times) { 94 /* broadcast */ 95 thred = cv->waitHead; 96 while (thred != NULL) { 97 thred->md.inCVWaitQueue = PR_FALSE; 98 thred = thred->md.next; 99 } 100 notified->cv[index].notifyHead = cv->waitHead; 101 cv->waitHead = cv->waitTail = NULL; 102 cv->nwait = 0; 103 } else { 104 thred = cv->waitHead; 105 i = notified->cv[index].times; 106 while (thred != NULL && i > 0) { 107 thred->md.inCVWaitQueue = PR_FALSE; 108 thred = thred->md.next; 109 i--; 110 } 111 notified->cv[index].notifyHead = cv->waitHead; 112 cv->waitHead = thred; 113 if (cv->waitHead == NULL) { 114 cv->waitTail = NULL; 115 } else { 116 if (cv->waitHead->md.prev != NULL) { 117 cv->waitHead->md.prev->md.next = NULL; 118 cv->waitHead->md.prev = NULL; 119 } 120 } 121 cv->nwait -= notified->cv[index].times - i; 122 } 123 } 124 notified = notified->link; 125 } while (NULL != notified); 126 127 if (waitThred) { 128 AddThreadToCVWaitQueueInternal(waitThred, waitCV); 129 } 130 131 /* Release the lock before notifying */ 132 LeaveCriticalSection(&lock->mutex); 133 134 notified = &post; /* this is where we start */ 135 do { 136 for (index = 0; index < notified->length; ++index) { 137 PRThread* thred; 138 PRThread* next; 139 140 thred = notified->cv[index].notifyHead; 141 while (thred != NULL) { 142 BOOL rv; 143 144 next = thred->md.next; 145 thred->md.prev = thred->md.next = NULL; 146 147 rv = ReleaseSemaphore(thred->md.blocked_sema, 1, NULL); 148 PR_ASSERT(rv != 0); 149 thred = next; 150 } 151 } 152 prev = notified; 153 notified = notified->link; 154 if (&post != prev) { 155 PR_DELETE(prev); 156 } 157 } while (NULL != notified); 158 } 159 160 /* 161 * Notifies just get posted to the protecting mutex. The 162 * actual notification is done when the lock is released so that 163 * MP systems don't contend for a lock that they can't have. 164 */ 165 static void md_PostNotifyToCvar(_MDCVar* cvar, _MDLock* lock, 166 PRBool broadcast) { 167 PRIntn index = 0; 168 _MDNotified* notified = &lock->notified; 169 170 while (1) { 171 for (index = 0; index < notified->length; ++index) { 172 if (notified->cv[index].cv == cvar) { 173 if (broadcast) { 174 notified->cv[index].times = -1; 175 } else if (-1 != notified->cv[index].times) { 176 notified->cv[index].times += 1; 177 } 178 return; 179 } 180 } 181 /* if not full, enter new CV in this array */ 182 if (notified->length < _MD_CV_NOTIFIED_LENGTH) { 183 break; 184 } 185 186 /* if there's no link, create an empty array and link it */ 187 if (NULL == notified->link) { 188 notified->link = PR_NEWZAP(_MDNotified); 189 } 190 191 notified = notified->link; 192 } 193 194 /* A brand new entry in the array */ 195 notified->cv[index].times = (broadcast) ? -1 : 1; 196 notified->cv[index].cv = cvar; 197 notified->length += 1; 198 } 199 200 /* 201 * _PR_MD_NEW_CV() -- Creating new condition variable 202 * ... Solaris uses cond_init() in similar function. 203 * 204 * returns: -1 on failure 205 * 0 when it succeeds. 206 * 207 */ 208 PRInt32 _PR_MD_NEW_CV(_MDCVar* cv) { 209 cv->magic = _MD_MAGIC_CV; 210 /* 211 * The waitHead, waitTail, and nwait fields are zeroed 212 * when the PRCondVar structure is created. 213 */ 214 return 0; 215 } 216 217 void _PR_MD_FREE_CV(_MDCVar* cv) { 218 cv->magic = (PRUint32)-1; 219 return; 220 } 221 222 /* 223 * _PR_MD_WAIT_CV() -- Wait on condition variable 224 */ 225 void _PR_MD_WAIT_CV(_MDCVar* cv, _MDLock* lock, PRIntervalTime timeout) { 226 PRThread* thred = _PR_MD_CURRENT_THREAD(); 227 DWORD rv; 228 DWORD msecs = (timeout == PR_INTERVAL_NO_TIMEOUT) 229 ? INFINITE 230 : PR_IntervalToMilliseconds(timeout); 231 232 /* 233 * If we have pending notifies, post them now. 234 */ 235 if (0 != lock->notified.length) { 236 md_UnlockAndPostNotifies(lock, thred, cv); 237 } else { 238 AddThreadToCVWaitQueueInternal(thred, cv); 239 LeaveCriticalSection(&lock->mutex); 240 } 241 242 /* Wait for notification or timeout; don't really care which */ 243 rv = WaitForSingleObject(thred->md.blocked_sema, msecs); 244 245 EnterCriticalSection(&(lock->mutex)); 246 247 PR_ASSERT(rv != WAIT_ABANDONED); 248 PR_ASSERT(rv != WAIT_FAILED); 249 PR_ASSERT(rv != WAIT_OBJECT_0 || thred->md.inCVWaitQueue == PR_FALSE); 250 251 if (rv == WAIT_TIMEOUT) { 252 if (thred->md.inCVWaitQueue) { 253 PR_ASSERT((cv->waitTail != NULL && cv->waitHead != NULL) || 254 (cv->waitTail == NULL && cv->waitHead == NULL)); 255 cv->nwait -= 1; 256 thred->md.inCVWaitQueue = PR_FALSE; 257 if (cv->waitHead == thred) { 258 cv->waitHead = thred->md.next; 259 if (cv->waitHead == NULL) { 260 cv->waitTail = NULL; 261 } else { 262 cv->waitHead->md.prev = NULL; 263 } 264 } else { 265 PR_ASSERT(thred->md.prev != NULL); 266 thred->md.prev->md.next = thred->md.next; 267 if (thred->md.next != NULL) { 268 thred->md.next->md.prev = thred->md.prev; 269 } else { 270 PR_ASSERT(cv->waitTail == thred); 271 cv->waitTail = thred->md.prev; 272 } 273 } 274 thred->md.next = thred->md.prev = NULL; 275 } else { 276 /* 277 * This thread must have been notified, but the 278 * ReleaseSemaphore call happens after WaitForSingleObject 279 * times out. Wait on the semaphore again to make it 280 * non-signaled. We assume this wait won't take long. 281 */ 282 rv = WaitForSingleObject(thred->md.blocked_sema, INFINITE); 283 PR_ASSERT(rv == WAIT_OBJECT_0); 284 } 285 } 286 PR_ASSERT(thred->md.inCVWaitQueue == PR_FALSE); 287 return; 288 } /* --- end _PR_MD_WAIT_CV() --- */ 289 290 void _PR_MD_NOTIFY_CV(_MDCVar* cv, _MDLock* lock) { 291 md_PostNotifyToCvar(cv, lock, PR_FALSE); 292 return; 293 } 294 295 void _PR_MD_NOTIFYALL_CV(_MDCVar* cv, _MDLock* lock) { 296 md_PostNotifyToCvar(cv, lock, PR_TRUE); 297 return; 298 } 299 300 typedef BOOL(WINAPI* INITIALIZECRITICALSECTIONEX)( 301 CRITICAL_SECTION* lpCriticalSection, DWORD dwSpinCount, DWORD Flags); 302 303 static INITIALIZECRITICALSECTIONEX sInitializeCriticalSectionEx; 304 305 void _PR_MD_INIT_LOCKS(void) { 306 /* 307 * Starting with Windows Vista, every CRITICAL_SECTION allocates an extra 308 * RTL_CRITICAL_SECTION_DEBUG object. Unfortunately, this debug object is 309 * not reclaimed by DeleteCriticalSection(), causing an apparent memory 310 * leak. This is a debugging "feature", not a bug. If we are running on 311 * Vista or later, use InitializeCriticalSectionEx() to allocate 312 * CRITICAL_SECTIONs without debug objects. 313 */ 314 HMODULE hKernel32 = GetModuleHandle("kernel32.dll"); 315 PR_ASSERT(hKernel32); 316 PR_ASSERT(!sInitializeCriticalSectionEx); 317 sInitializeCriticalSectionEx = (INITIALIZECRITICALSECTIONEX)GetProcAddress( 318 hKernel32, "InitializeCriticalSectionEx"); 319 } 320 321 /* 322 * By default, CRITICAL_SECTIONs are initialized with a spin count of 0. 323 * Joe Duffy's "Concurrent Programming on Windows" book suggests 1500 is 324 * a "reasonable starting point". On single-processor systems, the spin 325 * count is ignored and the critical section spin count is set to 0. 326 */ 327 #define LOCK_SPIN_COUNT 1500 328 329 PRStatus _PR_MD_NEW_LOCK(_MDLock* lock) { 330 CRITICAL_SECTION* cs = &lock->mutex; 331 BOOL ok; 332 333 if (sInitializeCriticalSectionEx) { 334 ok = sInitializeCriticalSectionEx(cs, LOCK_SPIN_COUNT, 335 CRITICAL_SECTION_NO_DEBUG_INFO); 336 } else { 337 ok = InitializeCriticalSectionAndSpinCount(cs, LOCK_SPIN_COUNT); 338 } 339 if (!ok) { 340 _PR_MD_MAP_DEFAULT_ERROR(GetLastError()); 341 return PR_FAILURE; 342 } 343 344 lock->notified.length = 0; 345 lock->notified.link = NULL; 346 return PR_SUCCESS; 347 } 348 349 void _PR_MD_UNLOCK(_MDLock* lock) { 350 if (0 != lock->notified.length) { 351 md_UnlockAndPostNotifies(lock, NULL, NULL); 352 } else { 353 LeaveCriticalSection(&lock->mutex); 354 } 355 }