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 #include "primpl.h"
7 
8 /************************************************************************/
9 
10 /*
11  * Notifies just get posted to the monitor. The actual notification is done
12  * when the monitor is fully exited so that MP systems don't contend for a
13  * monitor that they can't enter.
14  */
_PR_PostNotifyToMonitor(PRMonitor * mon,PRBool broadcast)15 static void _PR_PostNotifyToMonitor(PRMonitor *mon, PRBool broadcast)
16 {
17     PR_ASSERT(mon != NULL);
18     PR_ASSERT_CURRENT_THREAD_IN_MONITOR(mon);
19 
20     /* mon->notifyTimes is protected by the monitor, so we don't need to
21      * acquire mon->lock.
22      */
23     if (broadcast)
24         mon->notifyTimes = -1;
25     else if (mon->notifyTimes != -1)
26         mon->notifyTimes += 1;
27 }
28 
_PR_PostNotifiesFromMonitor(PRCondVar * cv,PRIntn times)29 static void _PR_PostNotifiesFromMonitor(PRCondVar *cv, PRIntn times)
30 {
31     PRStatus rv;
32 
33     /*
34      * Time to actually notify any waits that were affected while the monitor
35      * was entered.
36      */
37     PR_ASSERT(cv != NULL);
38     PR_ASSERT(times != 0);
39     if (times == -1) {
40         rv = PR_NotifyAllCondVar(cv);
41         PR_ASSERT(rv == PR_SUCCESS);
42     } else {
43         while (times-- > 0) {
44             rv = PR_NotifyCondVar(cv);
45             PR_ASSERT(rv == PR_SUCCESS);
46         }
47     }
48 }
49 
50 /*
51 ** Create a new monitor.
52 */
PR_NewMonitor()53 PR_IMPLEMENT(PRMonitor*) PR_NewMonitor()
54 {
55     PRMonitor *mon;
56     PRStatus rv;
57 
58     if (!_pr_initialized) _PR_ImplicitInitialization();
59 
60     mon = PR_NEWZAP(PRMonitor);
61     if (mon == NULL) {
62         PR_SetError(PR_OUT_OF_MEMORY_ERROR, 0);
63         return NULL;
64     }
65 
66     rv = _PR_InitLock(&mon->lock);
67     PR_ASSERT(rv == PR_SUCCESS);
68     if (rv != PR_SUCCESS)
69         goto error1;
70 
71     mon->owner = NULL;
72 
73     rv = _PR_InitCondVar(&mon->entryCV, &mon->lock);
74     PR_ASSERT(rv == PR_SUCCESS);
75     if (rv != PR_SUCCESS)
76         goto error2;
77 
78     rv = _PR_InitCondVar(&mon->waitCV, &mon->lock);
79     PR_ASSERT(rv == PR_SUCCESS);
80     if (rv != PR_SUCCESS)
81         goto error3;
82 
83     mon->notifyTimes = 0;
84     mon->entryCount = 0;
85     mon->name = NULL;
86     return mon;
87 
88 error3:
89     _PR_FreeCondVar(&mon->entryCV);
90 error2:
91     _PR_FreeLock(&mon->lock);
92 error1:
93     PR_Free(mon);
94     return NULL;
95 }
96 
PR_NewNamedMonitor(const char * name)97 PR_IMPLEMENT(PRMonitor*) PR_NewNamedMonitor(const char* name)
98 {
99     PRMonitor* mon = PR_NewMonitor();
100     if (mon)
101         mon->name = name;
102     return mon;
103 }
104 
105 /*
106 ** Destroy a monitor. There must be no thread waiting on the monitor's
107 ** condition variable. The caller is responsible for guaranteeing that the
108 ** monitor is no longer in use.
109 */
PR_DestroyMonitor(PRMonitor * mon)110 PR_IMPLEMENT(void) PR_DestroyMonitor(PRMonitor *mon)
111 {
112     PR_ASSERT(mon != NULL);
113     _PR_FreeCondVar(&mon->waitCV);
114     _PR_FreeCondVar(&mon->entryCV);
115     _PR_FreeLock(&mon->lock);
116 #if defined(DEBUG)
117     memset(mon, 0xaf, sizeof(PRMonitor));
118 #endif
119     PR_Free(mon);
120 }
121 
122 /*
123 ** Enter the lock associated with the monitor.
124 */
PR_EnterMonitor(PRMonitor * mon)125 PR_IMPLEMENT(void) PR_EnterMonitor(PRMonitor *mon)
126 {
127     PRThread *me = _PR_MD_CURRENT_THREAD();
128     PRStatus rv;
129 
130     PR_ASSERT(mon != NULL);
131     PR_Lock(&mon->lock);
132     if (mon->entryCount != 0) {
133         if (mon->owner == me)
134             goto done;
135         while (mon->entryCount != 0) {
136             rv = PR_WaitCondVar(&mon->entryCV, PR_INTERVAL_NO_TIMEOUT);
137             PR_ASSERT(rv == PR_SUCCESS);
138         }
139     }
140     /* and now I have the monitor */
141     PR_ASSERT(mon->notifyTimes == 0);
142     PR_ASSERT(mon->owner == NULL);
143     mon->owner = me;
144 
145 done:
146     mon->entryCount += 1;
147     rv = PR_Unlock(&mon->lock);
148     PR_ASSERT(rv == PR_SUCCESS);
149 }
150 
151 /*
152 ** Test and then enter the lock associated with the monitor if it's not
153 ** already entered by some other thread. Return PR_FALSE if some other
154 ** thread owned the lock at the time of the call.
155 */
PR_TestAndEnterMonitor(PRMonitor * mon)156 PR_IMPLEMENT(PRBool) PR_TestAndEnterMonitor(PRMonitor *mon)
157 {
158     PRThread *me = _PR_MD_CURRENT_THREAD();
159     PRStatus rv;
160 
161     PR_ASSERT(mon != NULL);
162     PR_Lock(&mon->lock);
163     if (mon->entryCount != 0) {
164         if (mon->owner == me)
165             goto done;
166         rv = PR_Unlock(&mon->lock);
167         PR_ASSERT(rv == PR_SUCCESS);
168         return PR_FALSE;
169     }
170     /* and now I have the monitor */
171     PR_ASSERT(mon->notifyTimes == 0);
172     PR_ASSERT(mon->owner == NULL);
173     mon->owner = me;
174 
175 done:
176     mon->entryCount += 1;
177     rv = PR_Unlock(&mon->lock);
178     PR_ASSERT(rv == PR_SUCCESS);
179     return PR_TRUE;
180 }
181 
182 /*
183 ** Exit the lock associated with the monitor once.
184 */
PR_ExitMonitor(PRMonitor * mon)185 PR_IMPLEMENT(PRStatus) PR_ExitMonitor(PRMonitor *mon)
186 {
187     PRThread *me = _PR_MD_CURRENT_THREAD();
188     PRStatus rv;
189 
190     PR_ASSERT(mon != NULL);
191     PR_Lock(&mon->lock);
192     /* the entries should be > 0 and we'd better be the owner */
193     PR_ASSERT(mon->entryCount > 0);
194     PR_ASSERT(mon->owner == me);
195     if (mon->entryCount == 0 || mon->owner != me)
196     {
197         rv = PR_Unlock(&mon->lock);
198         PR_ASSERT(rv == PR_SUCCESS);
199         return PR_FAILURE;
200     }
201 
202     mon->entryCount -= 1;  /* reduce by one */
203     if (mon->entryCount == 0)
204     {
205         /* and if it transitioned to zero - notify an entry waiter */
206         /* make the owner unknown */
207         mon->owner = NULL;
208         if (mon->notifyTimes != 0) {
209             _PR_PostNotifiesFromMonitor(&mon->waitCV, mon->notifyTimes);
210             mon->notifyTimes = 0;
211         }
212         rv = PR_NotifyCondVar(&mon->entryCV);
213         PR_ASSERT(rv == PR_SUCCESS);
214     }
215     rv = PR_Unlock(&mon->lock);
216     PR_ASSERT(rv == PR_SUCCESS);
217     return PR_SUCCESS;
218 }
219 
220 /*
221 ** Return the number of times that the current thread has entered the
222 ** lock. Returns zero if the current thread has not entered the lock.
223 */
PR_GetMonitorEntryCount(PRMonitor * mon)224 PR_IMPLEMENT(PRIntn) PR_GetMonitorEntryCount(PRMonitor *mon)
225 {
226     PRThread *me = _PR_MD_CURRENT_THREAD();
227     PRStatus rv;
228     PRIntn count = 0;
229 
230     PR_Lock(&mon->lock);
231     if (mon->owner == me)
232         count = mon->entryCount;
233     rv = PR_Unlock(&mon->lock);
234     PR_ASSERT(rv == PR_SUCCESS);
235     return count;
236 }
237 
PR_AssertCurrentThreadInMonitor(PRMonitor * mon)238 PR_IMPLEMENT(void) PR_AssertCurrentThreadInMonitor(PRMonitor *mon)
239 {
240 #if defined(DEBUG) || defined(FORCE_PR_ASSERT)
241     PRStatus rv;
242 
243     PR_Lock(&mon->lock);
244     PR_ASSERT(mon->entryCount != 0 &&
245               mon->owner == _PR_MD_CURRENT_THREAD());
246     rv = PR_Unlock(&mon->lock);
247     PR_ASSERT(rv == PR_SUCCESS);
248 #endif
249 }
250 
251 /*
252 ** Wait for a notify on the condition variable. Sleep for "ticks" amount
253 ** of time (if "tick" is 0 then the sleep is indefinite). While
254 ** the thread is waiting it exits the monitors lock (as if it called
255 ** PR_ExitMonitor as many times as it had called PR_EnterMonitor).  When
256 ** the wait has finished the thread regains control of the monitors lock
257 ** with the same entry count as before the wait began.
258 **
259 ** The thread waiting on the monitor will be resumed when the monitor is
260 ** notified (assuming the thread is the next in line to receive the
261 ** notify) or when the "ticks" elapses.
262 **
263 ** Returns PR_FAILURE if the caller has not locked the lock associated
264 ** with the condition variable.
265 ** This routine can return PR_PENDING_INTERRUPT_ERROR if the waiting thread
266 ** has been interrupted.
267 */
PR_Wait(PRMonitor * mon,PRIntervalTime ticks)268 PR_IMPLEMENT(PRStatus) PR_Wait(PRMonitor *mon, PRIntervalTime ticks)
269 {
270     PRStatus rv;
271     PRUint32 saved_entries;
272     PRThread *saved_owner;
273 
274     PR_ASSERT(mon != NULL);
275     PR_Lock(&mon->lock);
276     /* the entries better be positive */
277     PR_ASSERT(mon->entryCount > 0);
278     /* and it better be owned by us */
279     PR_ASSERT(mon->owner == _PR_MD_CURRENT_THREAD());  /* XXX return failure */
280 
281     /* tuck these away 'till later */
282     saved_entries = mon->entryCount;
283     mon->entryCount = 0;
284     saved_owner = mon->owner;
285     mon->owner = NULL;
286     /* If we have pending notifies, post them now. */
287     if (mon->notifyTimes != 0) {
288         _PR_PostNotifiesFromMonitor(&mon->waitCV, mon->notifyTimes);
289         mon->notifyTimes = 0;
290     }
291     rv = PR_NotifyCondVar(&mon->entryCV);
292     PR_ASSERT(rv == PR_SUCCESS);
293 
294     rv = PR_WaitCondVar(&mon->waitCV, ticks);
295     PR_ASSERT(rv == PR_SUCCESS);
296 
297     while (mon->entryCount != 0) {
298         rv = PR_WaitCondVar(&mon->entryCV, PR_INTERVAL_NO_TIMEOUT);
299         PR_ASSERT(rv == PR_SUCCESS);
300     }
301     PR_ASSERT(mon->notifyTimes == 0);
302     /* reinstate the interesting information */
303     mon->entryCount = saved_entries;
304     mon->owner = saved_owner;
305 
306     rv = PR_Unlock(&mon->lock);
307     PR_ASSERT(rv == PR_SUCCESS);
308     return rv;
309 }
310 
311 /*
312 ** Notify the highest priority thread waiting on the condition
313 ** variable. If a thread is waiting on the condition variable (using
314 ** PR_Wait) then it is awakened and begins waiting on the monitor's lock.
315 */
PR_Notify(PRMonitor * mon)316 PR_IMPLEMENT(PRStatus) PR_Notify(PRMonitor *mon)
317 {
318     _PR_PostNotifyToMonitor(mon, PR_FALSE);
319     return PR_SUCCESS;
320 }
321 
322 /*
323 ** Notify all of the threads waiting on the condition variable. All of
324 ** threads are notified in turn. The highest priority thread will
325 ** probably acquire the monitor first when the monitor is exited.
326 */
PR_NotifyAll(PRMonitor * mon)327 PR_IMPLEMENT(PRStatus) PR_NotifyAll(PRMonitor *mon)
328 {
329     _PR_PostNotifyToMonitor(mon, PR_TRUE);
330     return PR_SUCCESS;
331 }
332 
333 /************************************************************************/
334 
_PR_MonitorToString(PRMonitor * mon,char * buf,PRUint32 buflen)335 PRUint32 _PR_MonitorToString(PRMonitor *mon, char *buf, PRUint32 buflen)
336 {
337     PRUint32 nb;
338 
339     if (mon->owner) {
340 	nb = PR_snprintf(buf, buflen, "[%p] owner=%d[%p] count=%ld",
341 			 mon, mon->owner->id, mon->owner, mon->entryCount);
342     } else {
343 	nb = PR_snprintf(buf, buflen, "[%p]", mon);
344     }
345     return nb;
346 }
347