-
Notifications
You must be signed in to change notification settings - Fork 7
Description
Hello, this module works nice overall, but I'm having trouble when using it in unit tests.
The webgpu native addon crashes with a V8/NAPI fatal error when requestAdapter() within a Vitest runner, where each test file runs in a worker thread. The crash happens when the promise returned by requestAdapter() resolves. The addon's async completion callback runs, and GPUAdapter::Bind (or similar) attempts to create an EscapableHandleScope but fails.
Steps to reproduce
- Create a Vitest project with the webgpu package as a dependency.
- In
vitest.setup.js, injectnavigator.gpuviacreate([])from webgpu. - Write a test that calls
await navigator.gpu.requestAdapter()(orcreateGpuContext()which uses it) from within anit()test callback. - Run
vitest run.
NB: The crash only occurs when calling requestAdapter from within the test body. It does not occur when the same call is made from a beforeAll hook in the same worker (which is thus a possible workaround).
Example of error trace:
5: 0x10228c04c napi_open_escapable_handle_scope
6: 0x127dec98c Napi::EscapableHandleScope::EscapableHandleScope(Napi::Env)
7: 0x127dec7e8 Napi::FunctionReference::New(...)
8: 0x127e2d0ac wgpu::interop::GPUAdapter::Bind(...)
9: 0x127df0b08 wgpu::interop::Interface<...>::Create<...>
10: 0x127df0810 wgpu::binding::GPU::requestAdapter(...)
...
18: 0x1054b1290 Builtins_AsyncFunctionAwaitResolveClosure
19: 0x10557c4d8 Builtins_PromiseFulfillReactionJob
20: 0x1054a1594 Builtins_RunMicrotasks
...
34: 0x104769a70 node::InternalCallbackScope::Close
35: 0x104769cac node::InternalMakeCallback
36: 0x104780288 node::AsyncWrap::MakeCallback
37: 0x104980b74 node::StreamBase::CallJSOnreadMethod
38: 0x104982308 node::EmitToJSStreamListener::OnStreamRead
Tested environment:
- Package: webgpu@0.3.8
- Node.js: v22.19.0 (also observed on Node 20.x)
- OS: macOS (darwin)
- Test runner: Vitest 3.2.4 (uses worker threads via tinypool)
My Agent's 2 cents
I'm not familiar with the Node addon mechanism, so here is what my AI agent thinks of this issue FWIW:
Root cause (hypothesis)
The crash occurs when the addon's async completion runs in a callback context where V8's HandleScope is not properly set up. The stack trace shows the promise resolution happens during StreamBase::CallJSOnreadMethod—i.e., when Node is processing a stream read (likely from worker stdout/IPC). In that nested callback context, napi_open_escapable_handle_scope fails because the required HandleScope invariant is not satisfied.
Suggested fix
Ensure the addon's async completion callbacks always establish a proper HandleScope before creating any NAPI handles. For example, at the start of the callback that runs when requestAdapter or similar completes, the native code should call napi_open_handle_scope (or equivalent) before creating EscapableHandleScope or any other handles. This would make the addon robust to being invoked from arbitrary callback contexts (worker threads, stream callbacks, etc.).
References
- Similar issue with NAPI addons in worker threads: https://stackoverflow.com/questions/78843764/v8handlescopecreatehandle-cannot-create-a-handle-without-a-handlescope-wit
- node-serialport HandleScope issue: WorkerThreads: Cannot create a handle without a HandleScope serialport/node-serialport#1981