Skip to content

Commit 15a6bb9

Browse files
committed
fix(ffi): run block-disposal round-trip cleanup on the JS thread
Block disposal can run on an arbitrary thread (e.g. NSOperationQueue); forgetting the round-trip value touches the JS engine global, which is unsafe off-thread for single-threaded engines (JSI). Marshal the cleanup to the JS thread. Fixes intermittent hermes Promise cross-thread crash.
1 parent ec5a8c0 commit 15a6bb9

1 file changed

Lines changed: 21 additions & 5 deletions

File tree

NativeScript/ffi/shared/bridge/Callbacks.mm

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -521,13 +521,29 @@ bool releaseBlockCopy(const void* blockPointer) {
521521
released = true;
522522
}
523523
}
524-
// Block disposal can run during an autorelease-pool drain outside any JS
525-
// engine scope, so enter a runtime scope before touching the round-trip
526-
// root object (which reads the engine global/context).
524+
// Forgetting the round-trip value touches the JS engine global/context.
525+
// Block disposal can run during an autorelease-pool drain on an arbitrary
526+
// thread (e.g. an NSOperationQueue worker), so run the cleanup on the JS
527+
// thread (engines such as JSI are strictly single-threaded).
527528
if (released && bridge_ != nullptr && runtime_ != nullptr &&
528529
pointerToForget != nullptr) {
529-
NativeApiRuntimeScope runtimeScope(*runtime_);
530-
bridge_->forgetRoundTripValue(*runtime_, pointerToForget);
530+
auto bridge = bridge_;
531+
auto* runtime = runtime_;
532+
auto runtimeOwner = runtimeOwner_;
533+
const void* pointer = pointerToForget;
534+
auto forget = [bridge, runtime, runtimeOwner, pointer, keepAlive]() {
535+
NativeApiRuntimeScope runtimeScope(*runtime);
536+
bridge->forgetRoundTripValue(*runtime, pointer);
537+
};
538+
if (std::this_thread::get_id() == bridge_->jsThreadId()) {
539+
forget();
540+
} else if (const auto& invoker = bridge_->jsThreadCallbackInvoker()) {
541+
invoker(forget);
542+
} else if (auto scheduler = bridge_->scheduler()) {
543+
scheduler->invokeOnJS(forget);
544+
} else {
545+
forget();
546+
}
531547
}
532548
return released;
533549
}

0 commit comments

Comments
 (0)