You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
4. **Double-check**: This is critical to avoid the race where a reason was added just before `waiting_on_address` was set. If `wait_reasons` is now non-zero, clear `waiting_on_address` and go to step 1.
4. **Double-check**: This is critical to avoid the race where a reason was added just before `wait_address` was set. If `wait_reasons` is now non-zero, clear `wait_address` and go to step 1.
- Exit loop if `target->wake_counter != start_c` OR `target->wait_address != target_addr`.
90
90
- **Yield**: Call `sched_yield()` (or a small sleep) to allow the target thread to proceed if it is currently being scheduled.
91
91
92
92
### 4. Handling the Race Condition
93
93
The "Lost Wakeup" race is handled by the combination of:
94
-
- The waiter double-checking `wait_reasons` after publishing its `waiting_on_address`.
95
-
- The waker looping `atomic.wake` until the waiter increments its `wait_counter`.
94
+
- The waiter double-checking `wait_reasons` after publishing its `wait_address`.
95
+
- The waker looping `atomic.wake` until the waiter increments its `wake_counter`.
96
96
97
97
Even if the waker's first `atomic.wake` occurs after the waiter's double-check
98
98
but *before* the waiter actually enters the `atomic.wait` instruction, the waker
@@ -106,19 +106,20 @@ counter.
106
106
### 5. Overlapping and Spurious Wakeups
107
107
The design must handle cases where "real" wakeups (triggered by the application) and "side-channel" wakeups (cancellation/mailbox) occur simultaneously.
108
108
109
-
1. **Spurious Wakeups for Other Threads**: If multiple threads are waiting on the same address (e.g., a shared mutex), a side-channel `atomic_wake(addr, 1)` targeted at Thread A might be delivered by the kernel to Thread B.
110
-
- **Thread B's response**: It will wake up, increment its `wait_counter`, see that its `wait_reasons` are empty, and return `0` to its caller.
111
-
- **Thread C (the waker)**: It will see that Thread A's `wait_counter` has *not* changed and `waiting_on_address` is still `addr`. It will therefore continue its loop and call `atomic_wake` again until Thread A is finally woken.
109
+
1. **Spurious Wakeups for Other Threads**: If multiple threads are waiting on the same address (e.g., a shared mutex), a side-channel `atomic_wake(addr, 1)` targeting Thread A could also wake Thread B.
110
+
- **Thread B's response**: It will wake up, increment its `wake_counter`, see that its `wait_reasons` are empty, and return `0` to its caller.
111
+
- **Thread C (the waker)**: It will see that Thread A's `wake_counter` has *not* changed and `wait_address` is still `addr`. It will therefore continue its loop and call `atomic_wake` again until Thread A is finally woken.
112
112
- **Result**: Thread B experiences a "spurious" wakeup. This is acceptable and expected behavior for futex-based synchronization.
113
113
2. **Handling Side-Channel Success**: If Thread A is woken by the side-channel, it handles the event and returns `0`. The user's code will typically see that its own synchronization condition is not yet met and immediately call `emscripten_futex_wait` again. This effectively "resumes" the wait from the user's perspective while having allowed the side-channel event to be processed.
114
114
3. **No Lost "Real" Wakeups**: By returning to the caller whenever `atomic.wait` returns `OK`, we ensure that we never miss or swallow a real application-level `atomic.wake`.
115
115
116
116
### 6. Counter Wrap-around
117
-
The `wait_counter` is a `uint32_t` and will wrap around to zero after $2^{32}$ wakeups. This is safe because:
118
-
1. **Impossibility of Racing**: For the waker to "miss" a wake-up due to wrap-around, the waiter would have to wake up and re-enter a sleep state exactly $2^{32}$ times in the very brief window between the waker's `atomic_wake` and its subsequent check of `wait_counter`. Even at extreme wakeup frequencies (e.g., 1 million per second), this would take over an hour.
119
-
2. **Address Change Check**: The waker loop also checks `target->waiting_on_address != target_addr`. If the waiter wakes up and either stops waiting or starts waiting on a *different* address, the waker will exit the loop regardless of the counter value.
117
+
The `wake_counter` is a `uint32_t` and will wrap around to zero after $2^{32}$ wakeups. This is safe because:
118
+
1. **Impossibility of Racing**: For the waker to "miss" a wake-up due to wrap-around, the waiter would have to wake up and re-enter a sleep state exactly $2^{32}$ times in the very brief window between the waker's `atomic_wake` and its subsequent check of `wake_counter`. Even at extreme wakeup frequencies (e.g., 1 million per second), this would take over an hour.
119
+
2. **Address Change Check**: The waker loop also checks `target->wait_address != target_addr`. If the waiter wakes up and either stops waiting or starts waiting on a *different* address, the waker will exit the loop regardless of the counter value.
120
+
121
+
## Benefits
120
122
121
-
### 6. Benefits
122
123
- **Lower Power Consumption**: Threads can sleep indefinitely (or for the full duration of a user-requested timeout) without periodic wakeups.
123
124
- **Lower Latency**: Mailbox events and cancellation requests are processed immediately rather than waiting for the next 1ms or 100ms tick.
124
125
- **Simpler Loop**: The complex logic for calculating remaining timeout slices in `emscripten_futex_wait` is removed.
@@ -132,8 +133,8 @@ The `wait_counter` is a `uint32_t` and will wrap around to zero after $2^{32}$ w
132
133
design works around this by having the waker use the *user's* futex address.
133
134
134
135
## Security/Safety Considerations
135
-
- The `waiting_on_address` must be managed carefully to ensure wakers don't
136
-
call `atomic.wake` on stale addresses. The `wait_counter` and clearing the
136
+
- The `wait_address` must be managed carefully to ensure wakers don't
137
+
call `atomic.wake` on stale addresses. The `wake_counter` and clearing the
137
138
address upon wake mitigate this.
138
139
- The waker loop should have a reasonable fallback (like a yield) to prevent a
139
140
busy-wait deadlock if the waiter is somehow prevented from waking up (though
0 commit comments