|
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 * os2cv.c -- OS/2 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 |
|
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 void |
|
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 memset(&lock->notified, 0, 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 DosReleaseMutexSem(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 rv = DosPostEventSem(thred->md.blocked_sema); |
|
152 PR_ASSERT(rv == NO_ERROR); |
|
153 thred = next; |
|
154 } |
|
155 } |
|
156 prev = notified; |
|
157 notified = notified->link; |
|
158 if (&post != prev) PR_DELETE(prev); |
|
159 } while (NULL != notified); |
|
160 } |
|
161 |
|
162 /* |
|
163 * Notifies just get posted to the protecting mutex. The |
|
164 * actual notification is done when the lock is released so that |
|
165 * MP systems don't contend for a lock that they can't have. |
|
166 */ |
|
167 static void md_PostNotifyToCvar(_MDCVar *cvar, _MDLock *lock, |
|
168 PRBool broadcast) |
|
169 { |
|
170 PRIntn index = 0; |
|
171 _MDNotified *notified = &lock->notified; |
|
172 |
|
173 while (1) { |
|
174 for (index = 0; index < notified->length; ++index) { |
|
175 if (notified->cv[index].cv == cvar) { |
|
176 if (broadcast) { |
|
177 notified->cv[index].times = -1; |
|
178 } else if (-1 != notified->cv[index].times) { |
|
179 notified->cv[index].times += 1; |
|
180 } |
|
181 return; |
|
182 } |
|
183 } |
|
184 /* if not full, enter new CV in this array */ |
|
185 if (notified->length < _MD_CV_NOTIFIED_LENGTH) break; |
|
186 |
|
187 /* if there's no link, create an empty array and link it */ |
|
188 if (NULL == notified->link) { |
|
189 notified->link = PR_NEWZAP(_MDNotified); |
|
190 } |
|
191 |
|
192 notified = notified->link; |
|
193 } |
|
194 |
|
195 /* A brand new entry in the array */ |
|
196 notified->cv[index].times = (broadcast) ? -1 : 1; |
|
197 notified->cv[index].cv = cvar; |
|
198 notified->length += 1; |
|
199 } |
|
200 |
|
201 /* |
|
202 * _PR_MD_NEW_CV() -- Creating new condition variable |
|
203 * ... Solaris uses cond_init() in similar function. |
|
204 * |
|
205 * returns: -1 on failure |
|
206 * 0 when it succeeds. |
|
207 * |
|
208 */ |
|
209 PRInt32 |
|
210 _PR_MD_NEW_CV(_MDCVar *cv) |
|
211 { |
|
212 cv->magic = _MD_MAGIC_CV; |
|
213 /* |
|
214 * The waitHead, waitTail, and nwait fields are zeroed |
|
215 * when the PRCondVar structure is created. |
|
216 */ |
|
217 return 0; |
|
218 } |
|
219 |
|
220 void _PR_MD_FREE_CV(_MDCVar *cv) |
|
221 { |
|
222 cv->magic = (PRUint32)-1; |
|
223 return; |
|
224 } |
|
225 |
|
226 /* |
|
227 * _PR_MD_WAIT_CV() -- Wait on condition variable |
|
228 */ |
|
229 void |
|
230 _PR_MD_WAIT_CV(_MDCVar *cv, _MDLock *lock, PRIntervalTime timeout ) |
|
231 { |
|
232 PRThread *thred = _PR_MD_CURRENT_THREAD(); |
|
233 ULONG rv, count; |
|
234 ULONG msecs = (timeout == PR_INTERVAL_NO_TIMEOUT) ? |
|
235 SEM_INDEFINITE_WAIT : 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 DosReleaseMutexSem(lock->mutex); |
|
245 } |
|
246 |
|
247 /* Wait for notification or timeout; don't really care which */ |
|
248 rv = DosWaitEventSem(thred->md.blocked_sema, msecs); |
|
249 if (rv == NO_ERROR) { |
|
250 DosResetEventSem(thred->md.blocked_sema, &count); |
|
251 } |
|
252 |
|
253 DosRequestMutexSem((lock->mutex), SEM_INDEFINITE_WAIT); |
|
254 |
|
255 PR_ASSERT(rv == NO_ERROR || rv == ERROR_TIMEOUT); |
|
256 |
|
257 if(rv == ERROR_TIMEOUT) |
|
258 { |
|
259 if (thred->md.inCVWaitQueue) { |
|
260 PR_ASSERT((cv->waitTail != NULL && cv->waitHead != NULL) |
|
261 || (cv->waitTail == NULL && cv->waitHead == NULL)); |
|
262 cv->nwait -= 1; |
|
263 thred->md.inCVWaitQueue = PR_FALSE; |
|
264 if (cv->waitHead == thred) { |
|
265 cv->waitHead = thred->md.next; |
|
266 if (cv->waitHead == NULL) { |
|
267 cv->waitTail = NULL; |
|
268 } else { |
|
269 cv->waitHead->md.prev = NULL; |
|
270 } |
|
271 } else { |
|
272 PR_ASSERT(thred->md.prev != NULL); |
|
273 thred->md.prev->md.next = thred->md.next; |
|
274 if (thred->md.next != NULL) { |
|
275 thred->md.next->md.prev = thred->md.prev; |
|
276 } else { |
|
277 PR_ASSERT(cv->waitTail == thred); |
|
278 cv->waitTail = thred->md.prev; |
|
279 } |
|
280 } |
|
281 thred->md.next = thred->md.prev = NULL; |
|
282 } else { |
|
283 /* |
|
284 * This thread must have been notified, but the |
|
285 * SemRelease call happens after SemRequest |
|
286 * times out. Wait on the semaphore again to make it |
|
287 * non-signaled. We assume this wait won't take long. |
|
288 */ |
|
289 rv = DosWaitEventSem(thred->md.blocked_sema, SEM_INDEFINITE_WAIT); |
|
290 if (rv == NO_ERROR) { |
|
291 DosResetEventSem(thred->md.blocked_sema, &count); |
|
292 } |
|
293 PR_ASSERT(rv == NO_ERROR); |
|
294 } |
|
295 } |
|
296 PR_ASSERT(thred->md.inCVWaitQueue == PR_FALSE); |
|
297 return; |
|
298 } /* --- end _PR_MD_WAIT_CV() --- */ |
|
299 |
|
300 void |
|
301 _PR_MD_NOTIFY_CV(_MDCVar *cv, _MDLock *lock) |
|
302 { |
|
303 md_PostNotifyToCvar(cv, lock, PR_FALSE); |
|
304 return; |
|
305 } |
|
306 |
|
307 PRStatus |
|
308 _PR_MD_NEW_LOCK(_MDLock *lock) |
|
309 { |
|
310 DosCreateMutexSem(0, &(lock->mutex), 0, 0); |
|
311 (lock)->notified.length=0; |
|
312 (lock)->notified.link=NULL; |
|
313 return PR_SUCCESS; |
|
314 } |
|
315 |
|
316 void |
|
317 _PR_MD_NOTIFYALL_CV(_MDCVar *cv, _MDLock *lock) |
|
318 { |
|
319 md_PostNotifyToCvar(cv, lock, PR_TRUE); |
|
320 return; |
|
321 } |
|
322 |
|
323 void _PR_MD_UNLOCK(_MDLock *lock) |
|
324 { |
|
325 if (0 != lock->notified.length) { |
|
326 md_UnlockAndPostNotifies(lock, NULL, NULL); |
|
327 } else { |
|
328 DosReleaseMutexSem(lock->mutex); |
|
329 } |
|
330 } |