| /* | |
| * Copyright 2021 The Emscripten Authors. All rights reserved. | |
| * Emscripten is available under two separate licenses, the MIT license and the | |
| * University of Illinois/NCSA Open Source License. Both these licenses can be | |
| * found in the LICENSE file. | |
| */ | |
| extern "C" { | |
| void _wasmfs_thread_utils_heartbeat(em_proxying_queue* ctx); | |
| } | |
| namespace emscripten { | |
| // Helper class for synchronously proxying work to a dedicated worker thread, | |
| // including where the work is asynchronous. | |
| class ProxyWorker { | |
| // The queue we use to proxy work and the dedicated worker. | |
| ProxyingQueue queue; | |
| // Used to notify the calling thread once the worker has been started. | |
| bool started = false; | |
| std::mutex mutex; | |
| std::condition_variable cond; | |
| // Declare the thread last since it's dependent on the above member variables. | |
| // Declaring it last isn't strictly needed since the thread is initialized in | |
| // the body of the constructor, but is done out of caution. | |
| std::thread thread; | |
| public: | |
| // Spawn the worker thread. | |
| ProxyWorker() : queue() { | |
| // 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; | |
| } | |
| 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); | |
| // 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 | |
| // worry about potential deadlocks where it holds a lock while proxying an | |
| // operation and meanwhile the main thread is blocked trying to acquire the | |
| // same lock so is never able to spawn the worker thread. | |
| // | |
| // Unfortunately, this solution would cause the main thread to deadlock on | |
| // itself, so for now assert that we are not on the main thread. In the | |
| // future, we could provide an asynchronous version of this utility that | |
| // calls a user callback once the worker has been started. This asynchronous | |
| // version would be safe to use on the main thread. | |
| assert( | |
| !emscripten_is_main_browser_thread() && | |
| "cannot safely spawn dedicated workers from the main browser thread"); | |
| { | |
| std::unique_lock<std::mutex> lock(mutex); | |
| cond.wait(lock, [&]() { return started; }); | |
| } | |
| } | |
| // Kill the worker thread. | |
| ~ProxyWorker() { | |
| pthread_cancel(thread.native_handle()); | |
| thread.join(); | |
| } | |
| // Proxy synchronous work. | |
| void operator()(const std::function<void()>& func) { | |
| queue.proxySync(thread.native_handle(), func); | |
| } | |
| // Proxy asynchronous work that calls `finish()` on the ctx parameter to mark | |
| // its end. | |
| void operator()(const std::function<void(ProxyingQueue::ProxyingCtx)>& func) { | |
| queue.proxySyncWithCtx(thread.native_handle(), func); | |
| } | |
| }; | |
| } // namespace emscripten | |