summaryrefslogtreecommitdiff
path: root/nsprpub/pr/src/threads/prmon.c
blob: 36be8a9410bc7087908ac14aae7cff2d3e069e5d (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#include "primpl.h"

/************************************************************************/

/*
 * Notifies just get posted to the monitor. The actual notification is done
 * when the monitor is fully exited so that MP systems don't contend for a
 * monitor that they can't enter.
 */
static void _PR_PostNotifyToMonitor(PRMonitor *mon, PRBool broadcast)
{
    PR_ASSERT(mon != NULL);
    PR_ASSERT_CURRENT_THREAD_IN_MONITOR(mon);

    /* mon->notifyTimes is protected by the monitor, so we don't need to
     * acquire mon->lock.
     */
    if (broadcast)
        mon->notifyTimes = -1;
    else if (mon->notifyTimes != -1)
        mon->notifyTimes += 1;
}

static void _PR_PostNotifiesFromMonitor(PRCondVar *cv, PRIntn times)
{
    PRStatus rv;

    /*
     * Time to actually notify any waits that were affected while the monitor
     * was entered.
     */
    PR_ASSERT(cv != NULL);
    PR_ASSERT(times != 0);
    if (times == -1) {
        rv = PR_NotifyAllCondVar(cv);
        PR_ASSERT(rv == PR_SUCCESS);
    } else {
        while (times-- > 0) {
            rv = PR_NotifyCondVar(cv);
            PR_ASSERT(rv == PR_SUCCESS);
        }
    }
}

/*
** Create a new monitor.
*/
PR_IMPLEMENT(PRMonitor*) PR_NewMonitor()
{
    PRMonitor *mon;
    PRStatus rv;

    if (!_pr_initialized) _PR_ImplicitInitialization();

    mon = PR_NEWZAP(PRMonitor);
    if (mon == NULL) {
        PR_SetError(PR_OUT_OF_MEMORY_ERROR, 0);
        return NULL;
    }

    rv = _PR_InitLock(&mon->lock);
    PR_ASSERT(rv == PR_SUCCESS);
    if (rv != PR_SUCCESS)
        goto error1;

    mon->owner = NULL;

    rv = _PR_InitCondVar(&mon->entryCV, &mon->lock);
    PR_ASSERT(rv == PR_SUCCESS);
    if (rv != PR_SUCCESS)
        goto error2;

    rv = _PR_InitCondVar(&mon->waitCV, &mon->lock);
    PR_ASSERT(rv == PR_SUCCESS);
    if (rv != PR_SUCCESS)
        goto error3;

    mon->notifyTimes = 0;
    mon->entryCount = 0;
    mon->name = NULL;
    return mon;

error3:
    _PR_FreeCondVar(&mon->entryCV);
error2:
    _PR_FreeLock(&mon->lock);
error1:
    PR_Free(mon);
    return NULL;
}

PR_IMPLEMENT(PRMonitor*) PR_NewNamedMonitor(const char* name)
{
    PRMonitor* mon = PR_NewMonitor();
    if (mon)
        mon->name = name;
    return mon;
}

/*
** Destroy a monitor. There must be no thread waiting on the monitor's
** condition variable. The caller is responsible for guaranteeing that the
** monitor is no longer in use.
*/
PR_IMPLEMENT(void) PR_DestroyMonitor(PRMonitor *mon)
{
    PR_ASSERT(mon != NULL);
    _PR_FreeCondVar(&mon->waitCV);
    _PR_FreeCondVar(&mon->entryCV);
    _PR_FreeLock(&mon->lock);
#if defined(DEBUG)
    memset(mon, 0xaf, sizeof(PRMonitor));
#endif
    PR_Free(mon);
}

/*
** Enter the lock associated with the monitor.
*/
PR_IMPLEMENT(void) PR_EnterMonitor(PRMonitor *mon)
{
    PRThread *me = _PR_MD_CURRENT_THREAD();
    PRStatus rv;

    PR_ASSERT(mon != NULL);
    PR_Lock(&mon->lock);
    if (mon->entryCount != 0) {
        if (mon->owner == me)
            goto done;
        while (mon->entryCount != 0) {
            rv = PR_WaitCondVar(&mon->entryCV, PR_INTERVAL_NO_TIMEOUT);
            PR_ASSERT(rv == PR_SUCCESS);
        }
    }
    /* and now I have the monitor */
    PR_ASSERT(mon->notifyTimes == 0);
    PR_ASSERT(mon->owner == NULL);
    mon->owner = me;

done:
    mon->entryCount += 1;
    rv = PR_Unlock(&mon->lock);
    PR_ASSERT(rv == PR_SUCCESS);
}

/*
** Test and then enter the lock associated with the monitor if it's not
** already entered by some other thread. Return PR_FALSE if some other
** thread owned the lock at the time of the call.
*/
PR_IMPLEMENT(PRBool) PR_TestAndEnterMonitor(PRMonitor *mon)
{
    PRThread *me = _PR_MD_CURRENT_THREAD();
    PRStatus rv;

    PR_ASSERT(mon != NULL);
    PR_Lock(&mon->lock);
    if (mon->entryCount != 0) {
        if (mon->owner == me)
            goto done;
        rv = PR_Unlock(&mon->lock);
        PR_ASSERT(rv == PR_SUCCESS);
        return PR_FALSE;
    }
    /* and now I have the monitor */
    PR_ASSERT(mon->notifyTimes == 0);
    PR_ASSERT(mon->owner == NULL);
    mon->owner = me;

done:
    mon->entryCount += 1;
    rv = PR_Unlock(&mon->lock);
    PR_ASSERT(rv == PR_SUCCESS);
    return PR_TRUE;
}

/*
** Exit the lock associated with the monitor once.
*/
PR_IMPLEMENT(PRStatus) PR_ExitMonitor(PRMonitor *mon)
{
    PRThread *me = _PR_MD_CURRENT_THREAD();
    PRStatus rv;

    PR_ASSERT(mon != NULL);
    PR_Lock(&mon->lock);
    /* the entries should be > 0 and we'd better be the owner */
    PR_ASSERT(mon->entryCount > 0);
    PR_ASSERT(mon->owner == me);
    if (mon->entryCount == 0 || mon->owner != me)
    {
        rv = PR_Unlock(&mon->lock);
        PR_ASSERT(rv == PR_SUCCESS);
        return PR_FAILURE;
    }

    mon->entryCount -= 1;  /* reduce by one */
    if (mon->entryCount == 0)
    {
        /* and if it transitioned to zero - notify an entry waiter */
        /* make the owner unknown */
        mon->owner = NULL;
        if (mon->notifyTimes != 0) {
            _PR_PostNotifiesFromMonitor(&mon->waitCV, mon->notifyTimes);
            mon->notifyTimes = 0;
        }
        rv = PR_NotifyCondVar(&mon->entryCV);
        PR_ASSERT(rv == PR_SUCCESS);
    }
    rv = PR_Unlock(&mon->lock);
    PR_ASSERT(rv == PR_SUCCESS);
    return PR_SUCCESS;
}

/*
** Return the number of times that the current thread has entered the
** lock. Returns zero if the current thread has not entered the lock.
*/
PR_IMPLEMENT(PRIntn) PR_GetMonitorEntryCount(PRMonitor *mon)
{
    PRThread *me = _PR_MD_CURRENT_THREAD();
    PRStatus rv;
    PRIntn count = 0;

    PR_Lock(&mon->lock);
    if (mon->owner == me)
        count = mon->entryCount;
    rv = PR_Unlock(&mon->lock);
    PR_ASSERT(rv == PR_SUCCESS);
    return count;
}

PR_IMPLEMENT(void) PR_AssertCurrentThreadInMonitor(PRMonitor *mon)
{
#if defined(DEBUG) || defined(FORCE_PR_ASSERT)
    PRStatus rv;

    PR_Lock(&mon->lock);
    PR_ASSERT(mon->entryCount != 0 &&
              mon->owner == _PR_MD_CURRENT_THREAD());
    rv = PR_Unlock(&mon->lock);
    PR_ASSERT(rv == PR_SUCCESS);
#endif
}

/*
** Wait for a notify on the condition variable. Sleep for "ticks" amount
** of time (if "tick" is 0 then the sleep is indefinite). While
** the thread is waiting it exits the monitors lock (as if it called
** PR_ExitMonitor as many times as it had called PR_EnterMonitor).  When
** the wait has finished the thread regains control of the monitors lock
** with the same entry count as before the wait began.
**
** The thread waiting on the monitor will be resumed when the monitor is
** notified (assuming the thread is the next in line to receive the
** notify) or when the "ticks" elapses.
**
** Returns PR_FAILURE if the caller has not locked the lock associated
** with the condition variable.
** This routine can return PR_PENDING_INTERRUPT_ERROR if the waiting thread
** has been interrupted.
*/
PR_IMPLEMENT(PRStatus) PR_Wait(PRMonitor *mon, PRIntervalTime ticks)
{
    PRStatus rv;
    PRUint32 saved_entries;
    PRThread *saved_owner;

    PR_ASSERT(mon != NULL);
    PR_Lock(&mon->lock);
    /* the entries better be positive */
    PR_ASSERT(mon->entryCount > 0);
    /* and it better be owned by us */
    PR_ASSERT(mon->owner == _PR_MD_CURRENT_THREAD());  /* XXX return failure */

    /* tuck these away 'till later */
    saved_entries = mon->entryCount;
    mon->entryCount = 0;
    saved_owner = mon->owner;
    mon->owner = NULL;
    /* If we have pending notifies, post them now. */
    if (mon->notifyTimes != 0) {
        _PR_PostNotifiesFromMonitor(&mon->waitCV, mon->notifyTimes);
        mon->notifyTimes = 0;
    }
    rv = PR_NotifyCondVar(&mon->entryCV);
    PR_ASSERT(rv == PR_SUCCESS);

    rv = PR_WaitCondVar(&mon->waitCV, ticks);
    PR_ASSERT(rv == PR_SUCCESS);

    while (mon->entryCount != 0) {
        rv = PR_WaitCondVar(&mon->entryCV, PR_INTERVAL_NO_TIMEOUT);
        PR_ASSERT(rv == PR_SUCCESS);
    }
    PR_ASSERT(mon->notifyTimes == 0);
    /* reinstate the interesting information */
    mon->entryCount = saved_entries;
    mon->owner = saved_owner;

    rv = PR_Unlock(&mon->lock);
    PR_ASSERT(rv == PR_SUCCESS);
    return rv;
}

/*
** Notify the highest priority thread waiting on the condition
** variable. If a thread is waiting on the condition variable (using
** PR_Wait) then it is awakened and begins waiting on the monitor's lock.
*/
PR_IMPLEMENT(PRStatus) PR_Notify(PRMonitor *mon)
{
    _PR_PostNotifyToMonitor(mon, PR_FALSE);
    return PR_SUCCESS;
}

/*
** Notify all of the threads waiting on the condition variable. All of
** threads are notified in turn. The highest priority thread will
** probably acquire the monitor first when the monitor is exited.
*/
PR_IMPLEMENT(PRStatus) PR_NotifyAll(PRMonitor *mon)
{
    _PR_PostNotifyToMonitor(mon, PR_TRUE);
    return PR_SUCCESS;
}

/************************************************************************/

PRUint32 _PR_MonitorToString(PRMonitor *mon, char *buf, PRUint32 buflen)
{
    PRUint32 nb;

    if (mon->owner) {
	nb = PR_snprintf(buf, buflen, "[%p] owner=%d[%p] count=%ld",
			 mon, mon->owner->id, mon->owner, mon->entryCount);
    } else {
	nb = PR_snprintf(buf, buflen, "[%p]", mon);
    }
    return nb;
}