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
1820extern void * _emscripten_main_thread_futex ;
1921
2022static 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 and emscripten-built program in a Web
159+ // Worker. This 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 look 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 );
0 commit comments