From 91fab207ed2026dfc87ee4480b3d2dfc51033bcd Mon Sep 17 00:00:00 2001 From: ludamad Date: Mon, 16 Mar 2026 18:02:08 +0000 Subject: [PATCH] fix: move Release and delete out of BlockingCall callback to prevent SIGBUS on macOS MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Release() and delete were called inside the BlockingCall callback, which runs on the JS thread while BlockingCall is still blocked on the worker thread. Release() drops the TSFN refcount to 0, tearing down internal state that BlockingCall needs to unwind. delete destroys the TSFN member entirely. Both cause use-after-free when BlockingCall returns on the worker thread. macOS magazine malloc unmaps freed pages aggressively → SIGBUS. Fix: move both Release() and delete to after BlockingCall returns on the worker thread, where they can execute safely. --- .../src/barretenberg/nodejs_module/util/async_op.hpp | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/barretenberg/cpp/src/barretenberg/nodejs_module/util/async_op.hpp b/barretenberg/cpp/src/barretenberg/nodejs_module/util/async_op.hpp index 13a933cd5a8..76487bb69cc 100644 --- a/barretenberg/cpp/src/barretenberg/nodejs_module/util/async_op.hpp +++ b/barretenberg/cpp/src/barretenberg/nodejs_module/util/async_op.hpp @@ -114,7 +114,12 @@ class ThreadedAsyncOperation { _success = false; } - // Post completion back to the JS main thread + // Post completion back to the JS main thread. + // Release and delete must happen AFTER BlockingCall returns on this thread, + // not inside the callback. Release() drops the TSFN refcount to 0, which can + // tear down internal state (mutex/condvar) that BlockingCall still needs to + // unwind through. Doing either inside the callback caused SIGBUS on macOS + // (magazine malloc unmaps freed pages) and segfaults on Linux. _completion_tsfn.BlockingCall( this, [](Napi::Env env, Napi::Function /*js_callback*/, ThreadedAsyncOperation* op) { if (op->_success) { @@ -124,10 +129,9 @@ class ThreadedAsyncOperation { auto error = Napi::Error::New(env, op->_error); op->_deferred->Reject(error.Value()); } - // Release the TSFN and self-destruct - op->_completion_tsfn.Release(); - delete op; }); + _completion_tsfn.Release(); + delete this; }).detach(); }