Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 28 additions & 24 deletions system/lib/wasmfs/thread_utils.h
Original file line number Diff line number Diff line change
Expand Up @@ -34,32 +34,36 @@ class ProxyWorker {
public:
// Spawn the worker thread.
ProxyWorker()
: queue(), thread([&]() {
// Notify the caller that we have started.
{
std::unique_lock<std::mutex> lock(mutex);
started = true;
}
cond.notify_all();
: queue() {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does clang-format not put this onto a single line now?

// Initialize the thread in the constructor to ensure the object has been
// fully constructed before thread starts using the object to avoid a data
// race. See #24370.
thread = std::thread([&]() {
// Notify the caller that we have started.
{
std::unique_lock<std::mutex> lock(mutex);
started = true;
Comment on lines +44 to +45
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suspect this is still technically incorrect because the initialization write of started is not protected by the mutex, so it will not synchronize with this mutex-protected read on the worker thread. (Unless the std::thread constructor introduces some synchronization, but the documentation doesn't say.) Let's get rid of the lock and make the bool atomic in addition to the changes here.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

From what I interpret from the class initialization rules, started is guaranteed to be initialized before the thread is created, so we shouldn't need a lock at that point.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, initialized on the current thread, but that initialization write does not necessarily become visible on any other thread without some form of synchronization.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It took me a little while to dig this up, but thread creation is a synchronization point.

Initializing (unguarded) data before thread creation is pretty common pattern, so I was going to be surprised if that was actually UB.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice find, that makes sense.

}
cond.notify_all();

// Sometimes the main thread is spinning, waiting on a WasmFS lock held
// by a thread trying to proxy work to this dedicated worker. In that
// case, the proxying message won't be relayed by the main thread and
// the system will deadlock. This heartbeat ensures that proxying work
// eventually gets done so the thread holding the lock can make forward
// progress even if the main thread is blocked.
//
// TODO: Remove this once we can postMessage directly between workers
// without involving the main thread or once all browsers ship
// Atomics.waitAsync.
//
// Note that this requires adding _emscripten_proxy_execute_queue to
// EXPORTED_FUNCTIONS.
_wasmfs_thread_utils_heartbeat(queue.queue);
// Sometimes the main thread is spinning, waiting on a WasmFS lock held
// by a thread trying to proxy work to this dedicated worker. In that
// case, the proxying message won't be relayed by the main thread and
// the system will deadlock. This heartbeat ensures that proxying work
// eventually gets done so the thread holding the lock can make forward
// progress even if the main thread is blocked.
//
// TODO: Remove this once we can postMessage directly between workers
// without involving the main thread or once all browsers ship
// Atomics.waitAsync.
//
// Note that this requires adding _emscripten_proxy_execute_queue to
// EXPORTED_FUNCTIONS.
_wasmfs_thread_utils_heartbeat(queue.queue);

// Sit in the event loop performing work as it comes in.
emscripten_exit_with_live_runtime();
}) {
// Sit in the event loop performing work as it comes in.
emscripten_exit_with_live_runtime();
});

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