Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
emscripten-core
GitHub Repository: emscripten-core/emscripten
Path: blob/main/system/lib/pthread/emscripten_futex_wait.c
6172 views
1
/*
2
* Copyright 2021 The Emscripten Authors. All rights reserved.
3
* Emscripten is available under two separate licenses, the MIT license and the
4
* University of Illinois/NCSA Open Source License. Both these licenses can be
5
* found in the LICENSE file.
6
*/
7
#include <assert.h>
8
#include <errno.h>
9
#include <math.h>
10
#include <emscripten/threading.h>
11
#include <stdlib.h>
12
#include <stdio.h>
13
#include <sys/param.h>
14
#include "atomic.h"
15
#include "threading_internal.h"
16
#include "pthread_impl.h"
17
18
extern void* _emscripten_main_thread_futex;
19
20
static int futex_wait_main_browser_thread(volatile void* addr,
21
uint32_t val,
22
double timeout) {
23
// Atomics.wait is not available in the main browser thread, so simulate it
24
// via busy spinning. Only the main browser thread is allowed to call into
25
// this function. It is not thread-safe to be called from any other thread.
26
assert(emscripten_is_main_browser_thread());
27
28
double now = emscripten_get_now();
29
double end = now + timeout;
30
31
// Register globally which address the main thread is simulating to be waiting
32
// on. When zero, the main thread is not waiting on anything, and on nonzero,
33
// the contents of the address pointed by _emscripten_main_thread_futex tell
34
// which address the main thread is simulating its wait on. We need to be
35
// careful of recursion here: If we wait on a futex, and then call
36
// _emscripten_yield() below, that will call code that takes the proxying
37
// mutex - which can once more reach this code in a nested call. To avoid
38
// interference between the two (there is just a single
39
// _emscripten_main_thread_futex at a time), unmark ourselves before calling
40
// the potentially-recursive call. See below for how we handle the case of our
41
// futex being notified during the time in between when we are not set as the
42
// value of _emscripten_main_thread_futex.
43
void* last_addr = a_cas_p(&_emscripten_main_thread_futex, 0, (void*)addr);
44
// We must not have already been waiting.
45
assert(last_addr == 0);
46
47
while (1) {
48
// Check for a timeout.
49
now = emscripten_get_now();
50
if (now > end) {
51
// We timed out, so stop marking ourselves as waiting.
52
last_addr = a_cas_p(&_emscripten_main_thread_futex, (void*)addr, 0);
53
// The current value must have been our address which we set, or
54
// in a race it was set to 0 which means another thread just allowed
55
// us to run, but (tragically) that happened just a bit too late.
56
assert(last_addr == addr || last_addr == 0);
57
return -ETIMEDOUT;
58
}
59
// We are performing a blocking loop here, so we must handle proxied
60
// events from pthreads, to avoid deadlocks.
61
// Note that we have to do so carefully, as we may take a lock while
62
// doing so, which can recurse into this function; stop marking
63
// ourselves as waiting while we do so.
64
last_addr = a_cas_p(&_emscripten_main_thread_futex, (void*)addr, 0);
65
assert(last_addr == addr || last_addr == 0);
66
if (last_addr == 0) {
67
// We were told to stop waiting, so stop.
68
break;
69
}
70
_emscripten_yield(now);
71
72
// Check the value, as if we were starting the futex all over again.
73
// This handles the following case:
74
//
75
// * wait on futex A
76
// * recurse into _emscripten_yield(),
77
// which waits on futex B. that sets the _emscripten_main_thread_futex
78
// address to futex B, and there is no longer any mention of futex A.
79
// * a worker is done with futex A. it checks _emscripten_main_thread_futex
80
// but does not see A, so it does nothing special for the main thread.
81
// * a worker is done with futex B. it flips mainThreadMutex from B
82
// to 0, ending the wait on futex B.
83
// * we return to the wait on futex A. _emscripten_main_thread_futex is 0,
84
// but that is because of futex B being done - we can't tell from
85
// _emscripten_main_thread_futex whether A is done or not. therefore,
86
// check the memory value of the futex.
87
//
88
// That case motivates the design here. Given that, checking the memory
89
// address is also necessary for other reasons: we unset and re-set our
90
// address in _emscripten_main_thread_futex around calls to
91
// _emscripten_yield(), and a worker could
92
// attempt to wake us up right before/after such times.
93
//
94
// Note that checking the memory value of the futex is valid to do: we
95
// could easily have been delayed (relative to the worker holding on
96
// to futex A), which means we could be starting all of our work at the
97
// later time when there is no need to block. The only "odd" thing is
98
// that we may have caused side effects in that "delay" time. But the
99
// only side effects we can have are to call
100
// _emscripten_yield(). That is always ok to
101
// do on the main thread (it's why it is ok for us to call it in the
102
// middle of this function, and elsewhere). So if we check the value
103
// here and return, it's the same is if what happened on the main thread
104
// was the same as calling _emscripten_yield()
105
// a few times before calling emscripten_futex_wait().
106
if (__c11_atomic_load((_Atomic uint32_t*)addr, __ATOMIC_SEQ_CST) != val) {
107
return -EWOULDBLOCK;
108
}
109
110
// Mark us as waiting once more, and continue the loop.
111
last_addr = a_cas_p(&_emscripten_main_thread_futex, 0, (void*)addr);
112
assert(last_addr == 0);
113
}
114
return 0;
115
}
116
117
int emscripten_futex_wait(volatile void *addr, uint32_t val, double max_wait_ms) {
118
if ((((intptr_t)addr)&3) != 0) {
119
return -EINVAL;
120
}
121
122
// Pass 0 here, which means we don't have access to the current time in this
123
// function. This tells _emscripten_yield to call emscripten_get_now if (and
124
// only if) it needs to know the time.
125
_emscripten_yield(0);
126
127
int ret;
128
emscripten_conditional_set_current_thread_status(EM_THREAD_STATUS_RUNNING, EM_THREAD_STATUS_WAITFUTEX);
129
130
// For the main browser thread and audio worklets we can't use
131
// __builtin_wasm_memory_atomic_wait32 so we have busy wait instead.
132
if (!_emscripten_thread_supports_atomics_wait()) {
133
ret = futex_wait_main_browser_thread(addr, val, max_wait_ms);
134
emscripten_conditional_set_current_thread_status(EM_THREAD_STATUS_WAITFUTEX, EM_THREAD_STATUS_RUNNING);
135
return ret;
136
}
137
138
// -1 (or any negative number) means wait indefinitely.
139
int64_t max_wait_ns = -1;
140
if (max_wait_ms != INFINITY) {
141
max_wait_ns = (int64_t)(max_wait_ms*1000*1000);
142
}
143
#ifdef EMSCRIPTEN_DYNAMIC_LINKING
144
// After the main thread queues dlopen events, it checks if the target threads
145
// are sleeping.
146
// If `sleeping` is set then the main thread knows that event will be
147
// processed after the sleep (before any other user code). In this case the
148
// main thread does not wait for any kind of response form the thread.
149
// If `sleeping` is not set then we know we should wait for the thread process
150
// the queue, either from the call here directly after setting `sleeping` to
151
// 1, or from another callsite (e.g. the one in `emscripten_yield`).
152
int is_runtime_thread = emscripten_is_main_runtime_thread();
153
if (!is_runtime_thread) {
154
__pthread_self()->sleeping = 1;
155
_emscripten_process_dlopen_queue();
156
}
157
#endif
158
ret = __builtin_wasm_memory_atomic_wait32((int*)addr, val, max_wait_ns);
159
#ifdef EMSCRIPTEN_DYNAMIC_LINKING
160
if (!is_runtime_thread) {
161
__pthread_self()->sleeping = 0;
162
_emscripten_process_dlopen_queue();
163
}
164
#endif
165
166
done:
167
emscripten_conditional_set_current_thread_status(EM_THREAD_STATUS_WAITFUTEX, EM_THREAD_STATUS_RUNNING);
168
169
// memory.atomic.wait32 returns:
170
// 0 => "ok", woken by another agent.
171
// 1 => "not-equal", loaded value != expected value
172
// 2 => "timed-out", the timeout expired
173
if (ret == 1) {
174
return -EWOULDBLOCK;
175
}
176
if (ret == 2) {
177
return -ETIMEDOUT;
178
}
179
assert(ret == 0);
180
return 0;
181
}
182
183