Skip to content

Commit 65227c2

Browse files
committed
Move sleep slicing from musl's __wait to the lower level emscripten_futex_wait. NFC
This means we only need to do this breaking up on our `wait` operations in a single place. It also means that other users the `emscripten_futex_wait` API don't break pthread proxying or async cancellation. The moved code is only included in pthread-enabled builds so should not effect Wasm Workers builders. This change also paves the way for enabling musl's `__wait` to work with `WASM_WORKERS`.
1 parent 493e8bf commit 65227c2

File tree

6 files changed

+107
-66
lines changed

6 files changed

+107
-66
lines changed

system/lib/libc/musl/src/thread/__wait.c

Lines changed: 5 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -15,26 +15,11 @@ void __wait(volatile int *addr, volatile int *waiters, int val, int priv)
1515
}
1616
if (waiters) a_inc(waiters);
1717
#ifdef __EMSCRIPTEN__
18-
int is_runtime_thread = emscripten_is_main_runtime_thread();
19-
20-
// Main runtime thread may need to run proxied calls, so sleep in very small slices to be responsive.
21-
double max_ms_slice_to_sleep = is_runtime_thread ? 1 : 100;
22-
23-
while (*addr==val) {
24-
if (is_runtime_thread || pthread_self()->cancelasync == PTHREAD_CANCEL_ASYNCHRONOUS) {
25-
int e;
26-
do {
27-
if (pthread_self()->cancel) {
28-
if (waiters) a_dec(waiters);
29-
return;
30-
}
31-
// Must wait in slices in case this thread is cancelled in between.
32-
e = emscripten_futex_wait((void*)addr, val, max_ms_slice_to_sleep);
33-
} while (e == -ETIMEDOUT);
34-
} else {
35-
// Can wait in one go.
36-
emscripten_futex_wait((void*)addr, val, INFINITY);
37-
}
18+
// loop here to handler spurious wakeups from the underlying
19+
// emscripten_futex_wait.
20+
int ret = 0;
21+
while (*addr==val && ret == 0) {
22+
ret = emscripten_futex_wait((void*)addr, val, INFINITY);
3823
}
3924
#else
4025
while (*addr==val) {

system/lib/pthread/emscripten_futex_wait.c

Lines changed: 85 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ extern void* _emscripten_main_thread_futex;
1919

2020
static int futex_wait_main_browser_thread(volatile void* addr,
2121
uint32_t val,
22-
double timeout) {
22+
double timeout, bool cancelable) {
2323
// Atomics.wait is not available in the main browser thread, so simulate it
2424
// via busy spinning. Only the main browser thread is allowed to call into
2525
// this function. It is not thread-safe to be called from any other thread.
@@ -45,6 +45,12 @@ static int futex_wait_main_browser_thread(volatile void* addr,
4545
assert(last_addr == 0);
4646

4747
while (1) {
48+
#ifdef __EMSCRIPTEN_PTHREADS__
49+
// We if we were cancelled
50+
if (cancelable && pthread_self()->cancel) {
51+
return -ETIMEDOUT;
52+
}
53+
#endif
4854
// Check for a timeout.
4955
now = emscripten_get_now();
5056
if (now > end) {
@@ -114,68 +120,113 @@ static int futex_wait_main_browser_thread(volatile void* addr,
114120
return 0;
115121
}
116122

123+
// memory.atomic.wait32 returns:
124+
// 0 => "ok", woken by another agent.
125+
// 1 => "not-equal", loaded value != expected value
126+
// 2 => "timed-out", the timeout expired
127+
#define ATOMIC_WAIT_OK 0
128+
#define ATOMIC_WAIT_NOT_EQUAL 1
129+
#define ATOMIC_WAIT_TIMEOUT 2
130+
117131
int emscripten_futex_wait(volatile void *addr, uint32_t val, double max_wait_ms) {
118132
if ((((intptr_t)addr)&3) != 0) {
119133
return -EINVAL;
120134
}
121135

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-
127136
int ret;
128137
emscripten_conditional_set_current_thread_status(EM_THREAD_STATUS_RUNNING, EM_THREAD_STATUS_WAITFUTEX);
129138

139+
#ifdef __EMSCRIPTEN_PTHREADS__
140+
bool cancelable = pthread_self()->cancelasync == PTHREAD_CANCEL_ASYNCHRONOUS;
141+
#else
142+
bool cancelable = false;
143+
#endif
144+
130145
// For the main browser thread and audio worklets we can't use
131146
// __builtin_wasm_memory_atomic_wait32 so we have busy wait instead.
132147
if (!_emscripten_thread_supports_atomics_wait()) {
133-
ret = futex_wait_main_browser_thread(addr, val, max_wait_ms);
148+
ret = futex_wait_main_browser_thread(addr, val, max_wait_ms, cancelable);
134149
emscripten_conditional_set_current_thread_status(EM_THREAD_STATUS_WAITFUTEX, EM_THREAD_STATUS_RUNNING);
135150
return ret;
136151
}
137152

138153
// -1 (or any negative number) means wait indefinitely.
139154
int64_t max_wait_ns = -1;
140155
if (max_wait_ms != INFINITY) {
141-
max_wait_ns = (int64_t)(max_wait_ms*1000*1000);
156+
max_wait_ns = (int64_t)(max_wait_ms * 1e6);
142157
}
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();
158+
159+
#ifdef __EMSCRIPTEN_PTHREADS__
160+
// When building with pthread support there are two conditions underwhich we
161+
// need to limit the amount of time we spend in atomic.wait.
162+
// 1. We are the main runtime thread. In this case we need to be able to
163+
// process proxied events from workers. Note that this is not always
164+
// the same as being the main browser thread. For example, when running
165+
// under node or when launching and emscirpten-built program in a Web
166+
// Worker. This this case we limit our wait slices to 1ms intervals.
167+
// 2. When the current thread has async cancelation enabled. In this case
168+
// we limit the wait duration to 100ms intervals.
169+
int64_t wakeup_interval = 0;
170+
bool is_runtime_thread = emscripten_is_main_runtime_thread();
171+
if (is_runtime_thread) {
172+
// If the current thread is the main runtime theead then only wait in 1ms slices.
173+
wakeup_interval = 1 * 1e6;
174+
}
175+
else if (cancelable) {
176+
// If the current thread is async cancelable then only wait in 100ms slices.
177+
wakeup_interval = 100 * 1e6;
178+
}
179+
180+
int64_t interations;
181+
if (wakeup_interval) {
182+
interations = max_wait_ns / wakeup_interval;
183+
max_wait_ns = wakeup_interval;
156184
}
185+
186+
do {
157187
#endif
158-
ret = __builtin_wasm_memory_atomic_wait32((int*)addr, val, max_wait_ns);
188+
// Pass 0 here, which means we don't have access to the current time in this
189+
// function. This tells _emscripten_yield to call emscripten_get_now if (and
190+
// only if) it needs to know the time.
191+
_emscripten_yield(0);
192+
159193
#ifdef EMSCRIPTEN_DYNAMIC_LINKING
160-
if (!is_runtime_thread) {
161-
__pthread_self()->sleeping = 0;
162-
_emscripten_process_dlopen_queue();
163-
}
194+
// After the main thread queues dlopen events, it checks if the target threads
195+
// are sleeping.
196+
// If `sleeping` is set then the main thread knows that event will be
197+
// processed after the sleep (before any other user code). In this case the
198+
// main thread does not wait for any kind of response form the thread.
199+
// If `sleeping` is not set then we know we should wait for the thread process
200+
// the queue, either from the call here directly after setting `sleeping` to
201+
// 1, or from another callsite (e.g. the one in `emscripten_yield`).
202+
if (!is_runtime_thread) {
203+
__pthread_self()->sleeping = 1;
204+
_emscripten_process_dlopen_queue();
205+
}
206+
#endif
207+
ret = __builtin_wasm_memory_atomic_wait32((int*)addr, val, max_wait_ns);
208+
#ifdef EMSCRIPTEN_DYNAMIC_LINKING
209+
if (!is_runtime_thread) {
210+
__pthread_self()->sleeping = 0;
211+
_emscripten_process_dlopen_queue();
212+
}
213+
#endif
214+
#ifdef __EMSCRIPTEN_PTHREADS__
215+
if (cancelable && ret == ATOMIC_WAIT_TIMEOUT && pthread_self()->cancel) {
216+
// We were cancelled
217+
break;
218+
}
219+
} while (wakeup_interval && ret == ATOMIC_WAIT_TIMEOUT && (max_wait_ms == INFINITY || interations--));
164220
#endif
165221

166-
done:
167222
emscripten_conditional_set_current_thread_status(EM_THREAD_STATUS_WAITFUTEX, EM_THREAD_STATUS_RUNNING);
168223

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) {
224+
if (ret == ATOMIC_WAIT_NOT_EQUAL) {
174225
return -EWOULDBLOCK;
175226
}
176-
if (ret == 2) {
227+
if (ret == ATOMIC_WAIT_TIMEOUT) {
177228
return -ETIMEDOUT;
178229
}
179-
assert(ret == 0);
230+
assert(ret == ATOMIC_WAIT_OK);
180231
return 0;
181232
}

system/lib/pthread/library_pthread.c

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -87,8 +87,10 @@ void emscripten_thread_sleep(double msecs) {
8787
// If we have less than this many msecs left to wait, busy spin that instead.
8888
double min_ms_slice_to_sleep = 0.1;
8989

90-
// runtime thread may need to run proxied calls, so sleep in very small slices to be responsive.
91-
double max_ms_slice_to_sleep = emscripten_is_main_runtime_thread() ? 1 : 100;
90+
// Break up sleeping so that we process proxied work at regular intervals.
91+
// TODO(sbc): This should be remove and/or moved down into
92+
// `emscripten_futex_wait`.
93+
double max_ms_slice_to_sleep = 100;
9294

9395
emscripten_conditional_set_current_thread_status(
9496
EM_THREAD_STATUS_RUNNING, EM_THREAD_STATUS_SLEEPING);

test/codesize/test_codesize_minimal_pthreads.json

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
{
22
"a.out.js": 7884,
33
"a.out.js.gz": 3854,
4-
"a.out.nodebug.wasm": 19726,
5-
"a.out.nodebug.wasm.gz": 9135,
6-
"total": 27610,
7-
"total_gz": 12989,
4+
"a.out.nodebug.wasm": 19794,
5+
"a.out.nodebug.wasm.gz": 9214,
6+
"total": 27678,
7+
"total_gz": 13068,
88
"sent": [
99
"a (memory)",
1010
"b (emscripten_get_now)",

test/codesize/test_codesize_minimal_pthreads_memgrowth.json

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
{
22
"a.out.js": 8306,
33
"a.out.js.gz": 4059,
4-
"a.out.nodebug.wasm": 19727,
5-
"a.out.nodebug.wasm.gz": 9137,
6-
"total": 28033,
7-
"total_gz": 13196,
4+
"a.out.nodebug.wasm": 19795,
5+
"a.out.nodebug.wasm.gz": 9215,
6+
"total": 28101,
7+
"total_gz": 13274,
88
"sent": [
99
"a (memory)",
1010
"b (emscripten_get_now)",

tools/system_libs.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ def get_base_cflags(build_dir, force_object_files=False, preprocess=True):
6767
# caching of `pic` libraries (see `get_lib_dir` in `cache.py`)
6868
# FIXME(sbc): `-fPIC` should really be enough here.
6969
flags += ['-fPIC', '-sMAIN_MODULE']
70-
if preprocess:
70+
if not settings.WASM_WORKERS and preprocess:
7171
flags += ['-DEMSCRIPTEN_DYNAMIC_LINKING']
7272
if settings.MEMORY64:
7373
flags += ['-sMEMORY64']
@@ -735,7 +735,7 @@ def vary_on(cls):
735735
def get_default_variation(cls, **kwargs):
736736
return super().get_default_variation(
737737
is_mt=settings.PTHREADS,
738-
is_ww=settings.SHARED_MEMORY and not settings.PTHREADS,
738+
is_ww=settings.SHARED_MEMORY and not settings.PTHREADS and not settings.MAIN_MODULE,
739739
**kwargs,
740740
)
741741

@@ -746,6 +746,9 @@ def variations(cls):
746746
# These are mutually exclusive, only one flag will be set at any give time.
747747
return [combo for combo in combos if not combo['is_mt'] or not combo['is_ww']]
748748

749+
def can_build(self):
750+
return super().can_build() and not (settings.MAIN_MODULE and self.is_ww)
751+
749752

750753
class DebugLibrary(Library):
751754
def __init__(self, **kwargs):

0 commit comments

Comments
 (0)