Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
emscripten-core
GitHub Repository: emscripten-core/emscripten
Path: blob/main/system/lib/wasmfs/thread_utils.h
6174 views
1
/*
2
* Copyright 2021 The Emscripten Authors. All rights reserved.
3
* Emscripten is available under two separate licenses, the MIT license and the
4
* University of Illinois/NCSA Open Source License. Both these licenses can be
5
* found in the LICENSE file.
6
*/
7
8
#pragma once
9
10
#include <functional>
11
#include <thread>
12
13
#include <emscripten/proxying.h>
14
#include <emscripten/threading.h>
15
16
extern "C" {
17
void _wasmfs_thread_utils_heartbeat(em_proxying_queue* ctx);
18
}
19
20
namespace emscripten {
21
22
// Helper class for synchronously proxying work to a dedicated worker thread,
23
// including where the work is asynchronous.
24
class ProxyWorker {
25
// The queue we use to proxy work and the dedicated worker.
26
ProxyingQueue queue;
27
28
// Used to notify the calling thread once the worker has been started.
29
bool started = false;
30
std::mutex mutex;
31
std::condition_variable cond;
32
// Declare the thread last since it's dependent on the above member variables.
33
// Declaring it last isn't strictly needed since the thread is initialized in
34
// the body of the constructor, but is done out of caution.
35
std::thread thread;
36
37
public:
38
// Spawn the worker thread.
39
ProxyWorker() : queue() {
40
// Initialize the thread in the constructor to ensure the object has been
41
// fully constructed before thread starts using the object to avoid a data
42
// race. See #24370.
43
thread = std::thread([&]() {
44
// Notify the caller that we have started.
45
{
46
std::unique_lock<std::mutex> lock(mutex);
47
started = true;
48
}
49
cond.notify_all();
50
51
// Sometimes the main thread is spinning, waiting on a WasmFS lock held
52
// by a thread trying to proxy work to this dedicated worker. In that
53
// case, the proxying message won't be relayed by the main thread and
54
// the system will deadlock. This heartbeat ensures that proxying work
55
// eventually gets done so the thread holding the lock can make forward
56
// progress even if the main thread is blocked.
57
//
58
// TODO: Remove this once we can postMessage directly between workers
59
// without involving the main thread or once all browsers ship
60
// Atomics.waitAsync.
61
//
62
// Note that this requires adding _emscripten_proxy_execute_queue to
63
// EXPORTED_FUNCTIONS.
64
_wasmfs_thread_utils_heartbeat(queue.queue);
65
66
// Sit in the event loop performing work as it comes in.
67
emscripten_exit_with_live_runtime();
68
});
69
70
// Make sure the thread has actually started before returning. This allows
71
// subsequent code to assume the thread has already been spawned and not
72
// worry about potential deadlocks where it holds a lock while proxying an
73
// operation and meanwhile the main thread is blocked trying to acquire the
74
// same lock so is never able to spawn the worker thread.
75
//
76
// Unfortunately, this solution would cause the main thread to deadlock on
77
// itself, so for now assert that we are not on the main thread. In the
78
// future, we could provide an asynchronous version of this utility that
79
// calls a user callback once the worker has been started. This asynchronous
80
// version would be safe to use on the main thread.
81
assert(
82
!emscripten_is_main_browser_thread() &&
83
"cannot safely spawn dedicated workers from the main browser thread");
84
{
85
std::unique_lock<std::mutex> lock(mutex);
86
cond.wait(lock, [&]() { return started; });
87
}
88
}
89
90
// Kill the worker thread.
91
~ProxyWorker() {
92
pthread_cancel(thread.native_handle());
93
thread.join();
94
}
95
96
// Proxy synchronous work.
97
void operator()(const std::function<void()>& func) {
98
queue.proxySync(thread.native_handle(), func);
99
}
100
// Proxy asynchronous work that calls `finish()` on the ctx parameter to mark
101
// its end.
102
void operator()(const std::function<void(ProxyingQueue::ProxyingCtx)>& func) {
103
queue.proxySyncWithCtx(thread.native_handle(), func);
104
}
105
};
106
107
} // namespace emscripten
108
109