/* * 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. */ #pragma once #include #include #include #include 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 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 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& 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& func) { queue.proxySyncWithCtx(thread.native_handle(), func); } }; } // namespace emscripten