Skip to content

Commit 71c6000

Browse files
authored
[wasmfs] Fix data race in thread initialization. (#25114)
The `ProxyWorker` was creating a thread in the constructor initializer list and using a captured reference to the `started` member variable. This is problematic because it is not guaranteed that the class has been fully constructed during the initializer list. What happens: 1) `ProxyWorker` initializers run 2) Thread starts and sets `started = true` 3) `ProxyWorker` finishes construction and sets `started = false` 4) `ProxyWorker` waits for `started` to be `true` Step 4 will never finish in this case since the thread already ran. To fix this we simply need to move the thread creation into the constructor body where the class is guaranteed to be fully constructed. Fixes #24370, #24676, #20650
1 parent 19e295d commit 71c6000

File tree

1 file changed

+32
-26
lines changed

1 file changed

+32
-26
lines changed

system/lib/wasmfs/thread_utils.h

Lines changed: 32 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -24,42 +24,48 @@ namespace emscripten {
2424
class ProxyWorker {
2525
// The queue we use to proxy work and the dedicated worker.
2626
ProxyingQueue queue;
27-
std::thread thread;
2827

2928
// Used to notify the calling thread once the worker has been started.
3029
bool started = false;
3130
std::mutex mutex;
3231
std::condition_variable cond;
32+
// Declare the thread last since it's dependent on the above member variables.
33+
// Declaring it last isn't strictly needed since the thread is initialized in
34+
// the body of the constructor, but is done out of caution.
35+
std::thread thread;
3336

3437
public:
3538
// Spawn the worker thread.
36-
ProxyWorker()
37-
: queue(), thread([&]() {
38-
// Notify the caller that we have started.
39-
{
40-
std::unique_lock<std::mutex> lock(mutex);
41-
started = true;
42-
}
43-
cond.notify_all();
39+
ProxyWorker() : queue() {
40+
// Initialize the thread in the constructor to ensure the object has been
41+
// fully constructed before thread starts using the object to avoid a data
42+
// race. See #24370.
43+
thread = std::thread([&]() {
44+
// Notify the caller that we have started.
45+
{
46+
std::unique_lock<std::mutex> lock(mutex);
47+
started = true;
48+
}
49+
cond.notify_all();
4450

45-
// Sometimes the main thread is spinning, waiting on a WasmFS lock held
46-
// by a thread trying to proxy work to this dedicated worker. In that
47-
// case, the proxying message won't be relayed by the main thread and
48-
// the system will deadlock. This heartbeat ensures that proxying work
49-
// eventually gets done so the thread holding the lock can make forward
50-
// progress even if the main thread is blocked.
51-
//
52-
// TODO: Remove this once we can postMessage directly between workers
53-
// without involving the main thread or once all browsers ship
54-
// Atomics.waitAsync.
55-
//
56-
// Note that this requires adding _emscripten_proxy_execute_queue to
57-
// EXPORTED_FUNCTIONS.
58-
_wasmfs_thread_utils_heartbeat(queue.queue);
51+
// Sometimes the main thread is spinning, waiting on a WasmFS lock held
52+
// by a thread trying to proxy work to this dedicated worker. In that
53+
// case, the proxying message won't be relayed by the main thread and
54+
// the system will deadlock. This heartbeat ensures that proxying work
55+
// eventually gets done so the thread holding the lock can make forward
56+
// progress even if the main thread is blocked.
57+
//
58+
// TODO: Remove this once we can postMessage directly between workers
59+
// without involving the main thread or once all browsers ship
60+
// Atomics.waitAsync.
61+
//
62+
// Note that this requires adding _emscripten_proxy_execute_queue to
63+
// EXPORTED_FUNCTIONS.
64+
_wasmfs_thread_utils_heartbeat(queue.queue);
5965

60-
// Sit in the event loop performing work as it comes in.
61-
emscripten_exit_with_live_runtime();
62-
}) {
66+
// Sit in the event loop performing work as it comes in.
67+
emscripten_exit_with_live_runtime();
68+
});
6369

6470
// Make sure the thread has actually started before returning. This allows
6571
// subsequent code to assume the thread has already been spawned and not

0 commit comments

Comments
 (0)