Skip to content

Commit d36e8bc

Browse files
authored
Move sleep slicing from musl's __wait to the lower level emscripten_futex_wait. NFC (#26471)
This reduces the number of places we need to breaking up our `wait` operation. It also means that other users the `emscripten_futex_wait` API don't break pthread proxying or async cancellation. This change removes one more place where we were erroneously calling `pthread_self()` in the Wasm Workers build of libc, so this change also makes the code less broken in the Wasm Worker case. Needed as part of #26487
1 parent c166734 commit d36e8bc

File tree

6 files changed

+116
-65
lines changed

6 files changed

+116
-65
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 handle 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: 95 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -4,22 +4,24 @@
44
* University of Illinois/NCSA Open Source License. Both these licenses can be
55
* found in the LICENSE file.
66
*/
7+
8+
#include "atomic.h"
9+
#include "pthread_impl.h"
10+
#include "threading_internal.h"
11+
712
#include <assert.h>
13+
#include <emscripten/threading.h>
814
#include <errno.h>
915
#include <math.h>
10-
#include <emscripten/threading.h>
11-
#include <stdlib.h>
1216
#include <stdio.h>
17+
#include <stdlib.h>
1318
#include <sys/param.h>
14-
#include "atomic.h"
15-
#include "threading_internal.h"
16-
#include "pthread_impl.h"
1719

1820
extern void* _emscripten_main_thread_futex;
1921

2022
static int futex_wait_main_browser_thread(volatile void* addr,
2123
uint32_t val,
22-
double timeout) {
24+
double timeout, bool cancelable) {
2325
// Atomics.wait is not available in the main browser thread, so simulate it
2426
// via busy spinning. Only the main browser thread is allowed to call into
2527
// this function. It is not thread-safe to be called from any other thread.
@@ -45,6 +47,11 @@ static int futex_wait_main_browser_thread(volatile void* addr,
4547
assert(last_addr == 0);
4648

4749
while (1) {
50+
#ifdef __EMSCRIPTEN_PTHREADS__
51+
if (cancelable && pthread_self()->cancel) {
52+
return -ETIMEDOUT;
53+
}
54+
#endif
4855
// Check for a timeout.
4956
now = emscripten_get_now();
5057
if (now > end) {
@@ -119,48 +126,105 @@ int emscripten_futex_wait(volatile void *addr, uint32_t val, double max_wait_ms)
119126
return -EINVAL;
120127
}
121128

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-
127129
int ret;
128130
emscripten_conditional_set_current_thread_status(EM_THREAD_STATUS_RUNNING, EM_THREAD_STATUS_WAITFUTEX);
129131

132+
#ifdef __EMSCRIPTEN_PTHREADS__
133+
bool cancelable = pthread_self()->cancelasync == PTHREAD_CANCEL_ASYNCHRONOUS;
134+
#else
135+
bool cancelable = false;
136+
#endif
137+
130138
// For the main browser thread and audio worklets we can't use
131139
// __builtin_wasm_memory_atomic_wait32 so we have busy wait instead.
132140
if (!_emscripten_thread_supports_atomics_wait()) {
133-
ret = futex_wait_main_browser_thread(addr, val, max_wait_ms);
141+
ret = futex_wait_main_browser_thread(addr, val, max_wait_ms, cancelable);
134142
emscripten_conditional_set_current_thread_status(EM_THREAD_STATUS_WAITFUTEX, EM_THREAD_STATUS_RUNNING);
135143
return ret;
136144
}
137145

138146
// -1 (or any negative number) means wait indefinitely.
139147
int64_t max_wait_ns = ATOMICS_WAIT_DURATION_INFINITE;
140148
if (max_wait_ms != INFINITY) {
141-
max_wait_ns = (int64_t)(max_wait_ms*1000*1000);
149+
max_wait_ns = (int64_t)(max_wait_ms * 1e6);
142150
}
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();
151+
152+
#ifdef __EMSCRIPTEN_PTHREADS__
153+
// When building with pthread support there are two conditions under which we
154+
// need to limit the amount of time we spend in atomic.wait.
155+
// 1. We are the main runtime thread. In this case we need to be able to
156+
// process proxied events from workers. Note that this is not always
157+
// the same as being the main browser thread. For example, when running
158+
// under node or when launching an emscripten-built program in a Web
159+
// Worker. In this case we limit our wait slices to 1ms intervals.
160+
// 2. When the current thread has async cancellation enabled. In this case
161+
// we limit the wait duration to 100ms intervals.
162+
int64_t wakeup_interval = 0;
163+
bool is_runtime_thread = emscripten_is_main_runtime_thread();
164+
if (is_runtime_thread) {
165+
// If the current thread is the main runtime thread then only wait in 1ms slices.
166+
wakeup_interval = 1 * 1000000;
167+
}
168+
else if (cancelable) {
169+
// If the current thread is async cancellable then only wait in 100ms slices.
170+
wakeup_interval = 100 * 1000000;
156171
}
172+
173+
// When wakeup_interval is set, we use remainder_ns to track how many ns
174+
// remain of the intiial max_wait_ns.
175+
int64_t remainder_ns = 0;
176+
if (wakeup_interval) {
177+
remainder_ns = max_wait_ns;
178+
if (remainder_ns < 0) {
179+
max_wait_ns = wakeup_interval;
180+
} else {
181+
max_wait_ns = MIN(remainder_ns, wakeup_interval);
182+
}
183+
}
184+
185+
do {
157186
#endif
158-
ret = __builtin_wasm_memory_atomic_wait32((int*)addr, val, max_wait_ns);
187+
// Pass 0 here, which means we don't have access to the current time in this
188+
// function. This tells _emscripten_yield to call emscripten_get_now if (and
189+
// only if) it needs to know the time.
190+
_emscripten_yield(0);
191+
159192
#ifdef EMSCRIPTEN_DYNAMIC_LINKING
160-
if (!is_runtime_thread) {
161-
__pthread_self()->sleeping = 0;
162-
_emscripten_process_dlopen_queue();
163-
}
193+
// After the main thread queues dlopen events, it checks if the target threads
194+
// are sleeping.
195+
// If `sleeping` is set then the main thread knows that event will be
196+
// processed after the sleep (before any other user code). In this case the
197+
// main thread does not wait for any kind of response form the thread.
198+
// If `sleeping` is not set then we know we should wait for the thread process
199+
// the queue, either from the call here directly after setting `sleeping` to
200+
// 1, or from another callsite (e.g. the one in `emscripten_yield`).
201+
if (!is_runtime_thread) {
202+
__pthread_self()->sleeping = 1;
203+
_emscripten_process_dlopen_queue();
204+
}
205+
#endif
206+
ret = __builtin_wasm_memory_atomic_wait32((int*)addr, val, max_wait_ns);
207+
#ifdef EMSCRIPTEN_DYNAMIC_LINKING
208+
if (!is_runtime_thread) {
209+
__pthread_self()->sleeping = 0;
210+
_emscripten_process_dlopen_queue();
211+
}
212+
#endif
213+
#ifdef __EMSCRIPTEN_PTHREADS__
214+
if (cancelable && ret == ATOMICS_WAIT_TIMED_OUT && pthread_self()->cancel) {
215+
// Break out of the loop early if we were cancelled
216+
break;
217+
}
218+
// If remainder_ns is negative it means we want wait forever, and we don't
219+
// need to decrement remainder_ns in that case.
220+
if (wakeup_interval && remainder_ns > 0) {
221+
remainder_ns -= wakeup_interval;
222+
if (remainder_ns <= 0) {
223+
break;
224+
}
225+
max_wait_ns = MIN(remainder_ns, wakeup_interval);
226+
}
227+
} while (wakeup_interval && ret == ATOMICS_WAIT_TIMED_OUT);
164228
#endif
165229

166230
emscripten_conditional_set_current_thread_status(EM_THREAD_STATUS_WAITFUTEX, EM_THREAD_STATUS_RUNNING);

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 removed 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": 7364,
33
"a.out.js.gz": 3607,
4-
"a.out.nodebug.wasm": 19200,
5-
"a.out.nodebug.wasm.gz": 8859,
6-
"total": 26564,
7-
"total_gz": 12466,
4+
"a.out.nodebug.wasm": 19265,
5+
"a.out.nodebug.wasm.gz": 8934,
6+
"total": 26629,
7+
"total_gz": 12541,
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": 7766,
33
"a.out.js.gz": 3812,
4-
"a.out.nodebug.wasm": 19201,
5-
"a.out.nodebug.wasm.gz": 8860,
6-
"total": 26967,
7-
"total_gz": 12672,
4+
"a.out.nodebug.wasm": 19266,
5+
"a.out.nodebug.wasm.gz": 8935,
6+
"total": 27032,
7+
"total_gz": 12747,
88
"sent": [
99
"a (memory)",
1010
"b (emscripten_get_now)",

test/codesize/test_minimal_runtime_code_size_hello_wasm_worker.json

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@
33
"a.html.gz": 355,
44
"a.js": 956,
55
"a.js.gz": 605,
6-
"a.wasm": 2744,
7-
"a.wasm.gz": 1526,
8-
"total": 4215,
9-
"total_gz": 2486
6+
"a.wasm": 2584,
7+
"a.wasm.gz": 1438,
8+
"total": 4055,
9+
"total_gz": 2398
1010
}

0 commit comments

Comments
 (0)