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