Path: blob/main/system/lib/wasmfs/thread_utils.h
6174 views
/*1* Copyright 2021 The Emscripten Authors. All rights reserved.2* Emscripten is available under two separate licenses, the MIT license and the3* University of Illinois/NCSA Open Source License. Both these licenses can be4* found in the LICENSE file.5*/67#pragma once89#include <functional>10#include <thread>1112#include <emscripten/proxying.h>13#include <emscripten/threading.h>1415extern "C" {16void _wasmfs_thread_utils_heartbeat(em_proxying_queue* ctx);17}1819namespace emscripten {2021// Helper class for synchronously proxying work to a dedicated worker thread,22// including where the work is asynchronous.23class ProxyWorker {24// The queue we use to proxy work and the dedicated worker.25ProxyingQueue queue;2627// Used to notify the calling thread once the worker has been started.28bool started = false;29std::mutex mutex;30std::condition_variable cond;31// Declare the thread last since it's dependent on the above member variables.32// Declaring it last isn't strictly needed since the thread is initialized in33// the body of the constructor, but is done out of caution.34std::thread thread;3536public:37// Spawn the worker thread.38ProxyWorker() : queue() {39// Initialize the thread in the constructor to ensure the object has been40// fully constructed before thread starts using the object to avoid a data41// race. See #24370.42thread = std::thread([&]() {43// Notify the caller that we have started.44{45std::unique_lock<std::mutex> lock(mutex);46started = true;47}48cond.notify_all();4950// Sometimes the main thread is spinning, waiting on a WasmFS lock held51// by a thread trying to proxy work to this dedicated worker. In that52// case, the proxying message won't be relayed by the main thread and53// the system will deadlock. This heartbeat ensures that proxying work54// eventually gets done so the thread holding the lock can make forward55// progress even if the main thread is blocked.56//57// TODO: Remove this once we can postMessage directly between workers58// without involving the main thread or once all browsers ship59// Atomics.waitAsync.60//61// Note that this requires adding _emscripten_proxy_execute_queue to62// EXPORTED_FUNCTIONS.63_wasmfs_thread_utils_heartbeat(queue.queue);6465// Sit in the event loop performing work as it comes in.66emscripten_exit_with_live_runtime();67});6869// Make sure the thread has actually started before returning. This allows70// subsequent code to assume the thread has already been spawned and not71// worry about potential deadlocks where it holds a lock while proxying an72// operation and meanwhile the main thread is blocked trying to acquire the73// same lock so is never able to spawn the worker thread.74//75// Unfortunately, this solution would cause the main thread to deadlock on76// itself, so for now assert that we are not on the main thread. In the77// future, we could provide an asynchronous version of this utility that78// calls a user callback once the worker has been started. This asynchronous79// version would be safe to use on the main thread.80assert(81!emscripten_is_main_browser_thread() &&82"cannot safely spawn dedicated workers from the main browser thread");83{84std::unique_lock<std::mutex> lock(mutex);85cond.wait(lock, [&]() { return started; });86}87}8889// Kill the worker thread.90~ProxyWorker() {91pthread_cancel(thread.native_handle());92thread.join();93}9495// Proxy synchronous work.96void operator()(const std::function<void()>& func) {97queue.proxySync(thread.native_handle(), func);98}99// Proxy asynchronous work that calls `finish()` on the ctx parameter to mark100// its end.101void operator()(const std::function<void(ProxyingQueue::ProxyingCtx)>& func) {102queue.proxySyncWithCtx(thread.native_handle(), func);103}104};105106} // namespace emscripten107108109