@@ -19,7 +19,7 @@ extern void* _emscripten_main_thread_futex;
1919
2020static 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+
117131int 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}
0 commit comments