Barretenberg
The ZK-SNARK library at the core of Aztec
Loading...
Searching...
No Matches
async_op.hpp
Go to the documentation of this file.
1#pragma once
2
4#include <functional>
5#include <memory>
6#include <napi.h>
7#include <thread>
8#include <utility>
9
10#ifndef _WIN32
11#include <pthread.h>
12#endif
13
14namespace bb::nodejs {
15
16using async_fn = std::function<void(msgpack::sbuffer&)>;
17
33class AsyncOperation : public Napi::AsyncWorker {
34 public:
35 AsyncOperation(Napi::Env env, std::shared_ptr<Napi::Promise::Deferred> deferred, async_fn fn)
36 : Napi::AsyncWorker(env)
37 , _fn(std::move(fn))
38 , _deferred(std::move(deferred))
39 {}
40
45
46 ~AsyncOperation() override = default;
47
48 void Execute() override
49 {
50 try {
51 _fn(_result);
52 } catch (const std::exception& e) {
53 SetError(e.what());
54 } catch (...) {
55 // Catch any other exception type that's not derived from std::exception
56 // This ensures the promise is always rejected rather than leaving it hanging
57 SetError("Unknown exception occurred during async operation");
58 }
59 }
60
61 void OnOK() override
62 {
63 auto buf = Napi::Buffer<char>::Copy(Env(), _result.data(), _result.size());
64 _deferred->Resolve(buf);
65 }
66 void OnError(const Napi::Error& e) override { _deferred->Reject(e.Value()); }
67
68 private:
70 std::shared_ptr<Napi::Promise::Deferred> _deferred;
71 msgpack::sbuffer _result;
72};
73
93class ThreadedAsyncOperation : public std::enable_shared_from_this<ThreadedAsyncOperation> {
94 public:
95 ThreadedAsyncOperation(Napi::Env env, std::shared_ptr<Napi::Promise::Deferred> deferred, async_fn fn)
96 : _fn(std::move(fn))
97 , _deferred(std::move(deferred))
98 {
99 auto dummy = Napi::Function::New(env, [](const Napi::CallbackInfo&) {});
100 _completion_tsfn = Napi::ThreadSafeFunction::New(env, dummy, "ThreadedAsyncOpComplete", 0, 1);
101 }
102
107
109
110 static void Run(Napi::Env env, std::shared_ptr<Napi::Promise::Deferred> deferred, async_fn fn)
111 {
112 auto op = std::make_shared<ThreadedAsyncOperation>(env, std::move(deferred), std::move(fn));
113 op->Queue();
114 }
115
116 private:
117 // AVM simulation call chains are deep. Non-main threads get a 512 KB default stack on
118 // macOS versus 8 MB on Linux, so a default std::thread overflows its stack-guard page and
119 // aborts with SIGBUS on macOS arm64. The libuv pool that AsyncOperation runs on sizes its
120 // threads from RLIMIT_STACK, which is why that path never hit this. Pin a generous stack so
121 // the worker has the same headroom on every platform.
122 static constexpr size_t WORKER_STACK_SIZE = 32UL * 1024 * 1024;
123
124 void Queue()
125 {
126 auto self = shared_from_this();
128 try {
129 self->_fn(self->_result);
130 self->_success = true;
131 } catch (const std::exception& e) {
132 self->_error = e.what();
133 self->_success = false;
134 } catch (...) {
135 self->_error = "Unknown exception occurred during threaded async operation";
136 self->_success = false;
137 }
138
139 // Post completion to the JS main thread. The callback captures `self`
140 // (shared_ptr) so the object stays alive until the callback runs.
141 // napi_tsfn_blocking only blocks on queue insertion, not on callback
142 // completion, so we cannot use raw pointers here.
143 self->_completion_tsfn.BlockingCall([self](Napi::Env env, Napi::Function /*js_callback*/) {
144 if (self->_success) {
145 auto buf = Napi::Buffer<char>::Copy(env, self->_result.data(), self->_result.size());
146 self->_deferred->Resolve(buf);
147 } else {
148 auto error = Napi::Error::New(env, self->_error);
149 self->_deferred->Reject(error.Value());
150 }
151 self->_completion_tsfn.Release();
152 });
153 });
154 }
155
156 // Launch `work` on a detached OS thread with an explicitly large stack (see WORKER_STACK_SIZE).
157 // std::thread cannot set a stack size, so use pthreads where available and fall back to a
158 // default-stack std::thread only if pthread creation is unavailable or fails.
159 static void launch_detached_with_large_stack(std::function<void()> work)
160 {
161#ifndef _WIN32
162 pthread_attr_t attr;
163 if (pthread_attr_init(&attr) == 0) {
164 pthread_attr_setstacksize(&attr, WORKER_STACK_SIZE);
165 pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
166
167 auto* heap_work = new std::function<void()>(std::move(work));
168 pthread_t tid;
169 int rc = pthread_create(
170 &tid,
171 &attr,
172 [](void* arg) -> void* {
173 std::unique_ptr<std::function<void()>> fn(static_cast<std::function<void()>*>(arg));
174 (*fn)();
175 return nullptr;
176 },
177 heap_work);
178 pthread_attr_destroy(&attr);
179
180 if (rc == 0) {
181 return;
182 }
183
184 // pthread_create failed; reclaim the work and fall back to a default std::thread.
185 std::unique_ptr<std::function<void()>> reclaimed(heap_work);
186 work = std::move(*reclaimed);
187 }
188#endif
189 std::thread(std::move(work)).detach();
190 }
191
193 std::shared_ptr<Napi::Promise::Deferred> _deferred;
194 Napi::ThreadSafeFunction _completion_tsfn;
195 msgpack::sbuffer _result;
196 bool _success = false;
197 std::string _error;
198};
199
200} // namespace bb::nodejs
Encapsulatest some work that can be done off the JavaScript main thread.
Definition async_op.hpp:33
AsyncOperation & operator=(AsyncOperation &&)=delete
void Execute() override
Definition async_op.hpp:48
AsyncOperation(AsyncOperation &&)=delete
AsyncOperation & operator=(const AsyncOperation &)=delete
AsyncOperation(const AsyncOperation &)=delete
~AsyncOperation() override=default
AsyncOperation(Napi::Env env, std::shared_ptr< Napi::Promise::Deferred > deferred, async_fn fn)
Definition async_op.hpp:35
void OnError(const Napi::Error &e) override
Definition async_op.hpp:66
std::shared_ptr< Napi::Promise::Deferred > _deferred
Definition async_op.hpp:70
msgpack::sbuffer _result
Definition async_op.hpp:71
Runs work on a dedicated std::thread instead of the libuv thread pool.
Definition async_op.hpp:93
ThreadedAsyncOperation & operator=(const ThreadedAsyncOperation &)=delete
ThreadedAsyncOperation(Napi::Env env, std::shared_ptr< Napi::Promise::Deferred > deferred, async_fn fn)
Definition async_op.hpp:95
ThreadedAsyncOperation(const ThreadedAsyncOperation &)=delete
static void launch_detached_with_large_stack(std::function< void()> work)
Definition async_op.hpp:159
std::shared_ptr< Napi::Promise::Deferred > _deferred
Definition async_op.hpp:193
static void Run(Napi::Env env, std::shared_ptr< Napi::Promise::Deferred > deferred, async_fn fn)
Definition async_op.hpp:110
ThreadedAsyncOperation & operator=(ThreadedAsyncOperation &&)=delete
static constexpr size_t WORKER_STACK_SIZE
Definition async_op.hpp:122
Napi::ThreadSafeFunction _completion_tsfn
Definition async_op.hpp:194
ThreadedAsyncOperation(ThreadedAsyncOperation &&)=delete
std::function< void(msgpack::sbuffer &)> async_fn
Definition async_op.hpp:16
STL namespace.
constexpr decltype(auto) get(::tuplet::tuple< T... > &&t) noexcept
Definition tuple.hpp:13