You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Implement hermes_napi_host for async work and thread-safe functions (#398)
* Implement hermes_napi_host and pass it to hermes_napi_create_env
Provide the Phase 3 host integration for Hermes' first-party Node-API:
- New HermesNapiHost.{hpp,cpp}: a mirror of the hermes_napi_host struct
(pinned to HERMES_GIT_SHA) and a HostContext per React Native runtime,
backed by a process-global 4-thread worker pool (post_work /
cancel_work) and the runtime's CallInvoker behind a type-erased JS
dispatcher (post_task and work completions). fatal_exception
stringifies the error, logs and aborts; uv_loop and
ref_loop/unref_loop stay null by design. Contexts are retained for the
process lifetime because the env reads the struct during Runtime
teardown after env cleanup hooks have run.
- CxxNodeApiHostModule passes the host at env creation - before the
addon's init runs, fixing init-time async work - and drops
setCallInvoker.
- Delete the RuntimeNodeApiAsync overrides: async work falls through to
Hermes' implementation, so execute now runs on a worker thread instead
of the JS thread, and thread-safe functions work for the first time.
- tests/async: execute/complete thread-identity assertions, a gated
blocking execute (deadlock-proof that execute is off the JS thread)
and a deterministic cancel-of-running-work case.
- tests/threadsafe-function: port of Node's test_threadsafe_function
(pthread shim for uv threads, upstream assertions restored) plus
JS-thread and never-inline supplements; re-enable the
async_work_thread_safe_function example (its SIGABRT was the null
host).
- packages/host/tests: Catch2 suite exercising the worker pool,
cancellation atomicity, post_task ordering/reentrancy and the teardown
drop path on plain Linux, with a host-cpp-tests CI job mirroring
weak-node-api-tests.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Y6xHHeMFF853z5R6th5CkF
* Trigger CI for the label-gated device lanes
The Check workflow only reacts to opened/synchronize/reopened, so the
Apple and Android labels added to the PR need a synchronize event to be
seen by the job conditions.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Y6xHHeMFF853z5R6th5CkF
* Trigger CI with the weak-node-api and host labels applied
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Y6xHHeMFF853z5R6th5CkF
* Address review: truthful teardown outcomes, duplicate-queue drop
- JsDispatcher now reports acceptance and WorkItem holds its HostContext
strongly: the weak_ptr could never expire (contexts are retained for
the process lifetime), so the pool's drop branches were dead code and
napi_cancel_async_work could claim success for a completion the
dispatcher was about to drop. cancel_work now returns the dispatcher's
verdict, and workerMain/postTask log drops where they actually happen.
- WorkerPool::enqueue drops a double-queued (loopData, workData) instead
of enqueueing it: a second entry meant two completions for one
napi_async_work and a use-after-free once the addon deletes the work
inside the first. Covered by a new Catch2 test; the saturation helper
now uses distinct jobs per worker so it does not trip the detection.
- Delete HostContext copy/move: host_.data points at this.
- Justify the CallInvoker-liveness assumption at the dispatcher site
(RuntimeSchedulerCallInvoker holds a weak RuntimeScheduler owned
together with the runtime, so accepted work cannot outlive it) and
correct the WorkItem comment: the (loopData, workData) pair separates
runtimes/reloads, not envs.
- Rework the Catch2 teardown test to model an expired CallInvoker (the
state production reaches) instead of dropping the last context ref
(which it never does), and cover the rejected post_task path.
- Scope the 30s mocha timeout to the threadsafe-function suite so a
genuine deadlock elsewhere still fails fast; add a TODO on
fatal_exception about routing through RN error handling.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Y6xHHeMFF853z5R6th5CkF
---------
Co-authored-by: Claude <noreply@anthropic.com>
Provide a `hermes_napi_host` implementation to the Hermes Node-API environments. This enables thread-safe functions (`napi_create_threadsafe_function` and friends) and moves `napi_async_work` execution onto a worker pool — previously the `execute` callback ran on the JavaScript thread, blocking it for the duration of the work. The host is also in place before an addon's module init runs, so async work and thread-safe functions can now be created during initialization.
Copy file name to clipboardExpand all lines: docs/HOW-IT-WORKS.md
+1-1Lines changed: 1 addition & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -54,7 +54,7 @@ Hermes implements both halves of Node-API: the engine-specific functions (see [j
54
54
-`ref_loop` / `unref_loop` — keep the event loop alive while a thread-safe function is referenced, modelling libuv's "ref" semantics.
55
55
-`fatal_exception` and, for embedders that have one, a libuv loop pointer for `napi_get_uv_event_loop`.
56
56
57
-
`react-native-node-api` provides that struct, backed by React Native's `CallInvoker` for anything that has to land on the JavaScript thread and a worker pool for the rest.
57
+
`react-native-node-api` provides that struct (see `packages/host/cpp/HermesNapiHost.cpp`), backed by React Native's `CallInvoker` for anything that has to land on the JavaScript thread and a process-global worker pool (four threads, like libuv's default) for the rest. `ref_loop` / `unref_loop` and the libuv loop pointer are deliberately left null: React Native's JavaScript thread has no ref-counted event-loop lifetime to model, so thread-safe function ref/unref are tracked but inert, and `napi_get_uv_event_loop` returns `napi_generic_failure` as upstream documents for hosts without libuv.
0 commit comments