Skip to content

Conversation

brendandahl
Copy link
Collaborator

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

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 emscripten-core#24370, emscripten-core#24676, emscripten-core#20650
@brendandahl brendandahl requested review from sbc100 and tlively August 29, 2025 23:11
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?

@sbc100
Copy link
Collaborator

sbc100 commented Aug 29, 2025

Nice work!!!!

@brendandahl
Copy link
Collaborator Author

As mentioned in chat, I tried fixing this by moving started to the top of the decls list, but that didn't work. After thinking about it, I realized the thread also relies on the mutex and and condition variable too. I tried moving std::thread thread declaration to the end and that also fixes the issue.

Anyone have a preference on solution? I lean towards the current PR since any new members and dependencies will work as expected.

@sbc100
Copy link
Collaborator

sbc100 commented Sep 2, 2025

I think using the correct decl order is better, but please also a comment about the decl order being important.

@sbc100
Copy link
Collaborator

sbc100 commented Sep 2, 2025

Actually maybe apply both fixes, since didn't you also read the starting threads in initializers is bad? Either way please add comments in the decl order.

Comment on lines +44 to +45
std::unique_lock<std::mutex> lock(mutex);
started = true;
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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

wasmfs_create_opfs_backend() hangs on low end devices
3 participants