8
8
#include <errno.h>
9
9
#include <math.h>
10
10
#include <emscripten/threading.h>
11
+ #include "atomic.h"
11
12
#include "threading_internal.h"
12
13
14
+ extern void * _emscripten_main_thread_futex ;
15
+
13
16
int _emscripten_thread_supports_atomics_wait (void );
14
- int _emscripten_futex_wait_non_blocking (volatile void * addr , uint32_t val , double max_wait_ms );
17
+
18
+ static int futex_wait_busy (volatile void * addr , uint32_t val , double timeout ) {
19
+ // Atomics.wait is not available in the main browser thread, so simulate it via busy spinning.
20
+ double now = emscripten_get_now ();
21
+ double end = now + timeout ;
22
+
23
+ // Register globally which address the main thread is simulating to be
24
+ // waiting on. When zero, the main thread is not waiting on anything, and on
25
+ // nonzero, the contents of the address pointed by __emscripten_main_thread_futex
26
+ // tell which address the main thread is simulating its wait on.
27
+ // We need to be careful of recursion here: If we wait on a futex, and
28
+ // then call _emscripten_main_thread_process_queued_calls() below, that
29
+ // will call code that takes the proxying mutex - which can once more
30
+ // reach this code in a nested call. To avoid interference between the
31
+ // two (there is just a single __emscripten_main_thread_futex at a time), unmark
32
+ // ourselves before calling the potentially-recursive call. See below for
33
+ // how we handle the case of our futex being notified during the time in
34
+ // between when we are not set as the value of __emscripten_main_thread_futex.
35
+ void * last_addr = a_cas_p (& _emscripten_main_thread_futex , 0 , (void * )addr );
36
+ // We must not have already been waiting.
37
+ assert (last_addr == 0 );
38
+
39
+ while (1 ) {
40
+ // Check for a timeout.
41
+ now = emscripten_get_now ();
42
+ if (now > end ) {
43
+ // We timed out, so stop marking ourselves as waiting.
44
+ last_addr = a_cas_p (& _emscripten_main_thread_futex , (void * )addr , 0 );
45
+ // The current value must have been our address which we set, or
46
+ // in a race it was set to 0 which means another thread just allowed
47
+ // us to run, but (tragically) that happened just a bit too late.
48
+ assert (last_addr == addr || last_addr == 0 );
49
+ return - ETIMEDOUT ;
50
+ }
51
+ // We are performing a blocking loop here, so we must handle proxied
52
+ // events from pthreads, to avoid deadlocks.
53
+ // Note that we have to do so carefully, as we may take a lock while
54
+ // doing so, which can recurse into this function; stop marking
55
+ // ourselves as waiting while we do so.
56
+ last_addr = a_cas_p (& _emscripten_main_thread_futex , (void * )addr , 0 );
57
+ assert (last_addr == addr || last_addr == 0 );
58
+ if (last_addr == 0 ) {
59
+ // We were told to stop waiting, so stop.
60
+ break ;
61
+ }
62
+ emscripten_main_thread_process_queued_calls ();
63
+
64
+ // Check the value, as if we were starting the futex all over again.
65
+ // This handles the following case:
66
+ //
67
+ // * wait on futex A
68
+ // * recurse into emscripten_main_thread_process_queued_calls(),
69
+ // which waits on futex B. that sets the __emscripten_main_thread_futex address to
70
+ // futex B, and there is no longer any mention of futex A.
71
+ // * a worker is done with futex A. it checks __emscripten_main_thread_futex but does
72
+ // not see A, so it does nothing special for the main thread.
73
+ // * a worker is done with futex B. it flips mainThreadMutex from B
74
+ // to 0, ending the wait on futex B.
75
+ // * we return to the wait on futex A. __emscripten_main_thread_futex is 0, but that
76
+ // is because of futex B being done - we can't tell from
77
+ // __emscripten_main_thread_futex whether A is done or not. therefore, check the
78
+ // memory value of the futex.
79
+ //
80
+ // That case motivates the design here. Given that, checking the memory
81
+ // address is also necessary for other reasons: we unset and re-set our
82
+ // address in __emscripten_main_thread_futex around calls to
83
+ // emscripten_main_thread_process_queued_calls(), and a worker could
84
+ // attempt to wake us up right before/after such times.
85
+ //
86
+ // Note that checking the memory value of the futex is valid to do: we
87
+ // could easily have been delayed (relative to the worker holding on
88
+ // to futex A), which means we could be starting all of our work at the
89
+ // later time when there is no need to block. The only "odd" thing is
90
+ // that we may have caused side effects in that "delay" time. But the
91
+ // only side effects we can have are to call
92
+ // emscripten_main_thread_process_queued_calls(). That is always ok to
93
+ // do on the main thread (it's why it is ok for us to call it in the
94
+ // middle of this function, and elsewhere). So if we check the value
95
+ // here and return, it's the same is if what happened on the main thread
96
+ // was the same as calling emscripten_main_thread_process_queued_calls()
97
+ // a few times before calling emscripten_futex_wait().
98
+ if (__c11_atomic_load ((_Atomic uintptr_t * )addr , __ATOMIC_SEQ_CST ) != val ) {
99
+ return - EWOULDBLOCK ;
100
+ }
101
+
102
+ // Mark us as waiting once more, and continue the loop.
103
+ last_addr = a_cas_p (& _emscripten_main_thread_futex , 0 , (void * )addr );
104
+ assert (last_addr == 0 );
105
+ }
106
+ return 0 ;
107
+ }
15
108
16
109
int emscripten_futex_wait (volatile void * addr , uint32_t val , double max_wait_ms ) {
17
110
if ((((intptr_t )addr )& 3 ) != 0 ) {
@@ -25,7 +118,7 @@ int emscripten_futex_wait(volatile void *addr, uint32_t val, double max_wait_ms)
25
118
// __builtin_wasm_memory_atomic_wait32 so we call out the JS function that
26
119
// will busy wait.
27
120
if (!_emscripten_thread_supports_atomics_wait ()) {
28
- ret = _emscripten_futex_wait_non_blocking (addr , val , max_wait_ms );
121
+ ret = futex_wait_busy (addr , val , max_wait_ms );
29
122
emscripten_conditional_set_current_thread_status (EM_THREAD_STATUS_WAITFUTEX , EM_THREAD_STATUS_RUNNING );
30
123
return ret ;
31
124
}
0 commit comments