Skip to content

Fatal error when using N-API async code from process exit hooks #591

@rolftimmermans

Description

@rolftimmermans

When calling N-API based async code (in particular written with none-addon-api) from a process exit hook process.on("exit") it leads to a fatal error.

This is very easy to reproduce. For example, take the asyncworker.js tests and put the last block within a process.on("exit") callback. Like this

Running as node --expose-gc test/asyncworker.js the process is aborted and I see the following stack trace. Tested with Node.js 12.12.0 on macOS 10.14.

FATAL ERROR: Error::Error napi_create_reference
 1: 0x100b6a52a node::Abort() (.cold.1) [/usr/local/bin/node]
 2: 0x100081f70 node::FatalError(char const*, char const*) [/usr/local/bin/node]
 3: 0x100082098 node::OnFatalError(char const*, char const*) [/usr/local/bin/node]
 4: 0x100081f79 node::OnFatalError(char const*, char const*) [/usr/local/bin/node]
 5: 0x100060554 napi_open_callback_scope [/usr/local/bin/node]
 6: 0x104c01746 Napi::Error::Fatal(char const*, char const*) [/Users/rolftimmermans/Code/vendor/node-addon-api/test/build/Release/binding.node]
 7: 0x104c0588b Napi::Error::New(napi_env__*) [/Users/rolftimmermans/Code/vendor/node-addon-api/test/build/Release/binding.node]
 8: 0x104c066db Napi::FunctionReference::Call(napi_value__*, std::initializer_list<napi_value__*> const&) const [/Users/rolftimmermans/Code/vendor/node-addon-api/test/build/Release/binding.node]
 9: 0x104c06e42 Napi::AsyncWorker::OnError(Napi::Error const&) [/Users/rolftimmermans/Code/vendor/node-addon-api/test/build/Release/binding.node]
10: 0x104c05d59 Napi::AsyncWorker::OnWorkComplete(napi_env__*, napi_status, void*)::'lambda'()::operator()() const [/Users/rolftimmermans/Code/vendor/node-addon-api/test/build/Release/binding.node]
11: 0x104c04191 Napi::AsyncWorker::OnWorkComplete(napi_env__*, napi_status, void*) [/Users/rolftimmermans/Code/vendor/node-addon-api/test/build/Release/binding.node]
12: 0x100061e1d (anonymous namespace)::uvimpl::Work::AfterThreadPoolWork(int) [/usr/local/bin/node]
13: 0x100691aec uv__work_done [/usr/local/bin/node]
14: 0x100695037 uv__async_io [/usr/local/bin/node]
15: 0x1006a4426 uv__io_poll [/usr/local/bin/node]
16: 0x100695472 uv_run [/usr/local/bin/node]
17: 0x10003ddb2 node::Environment::CleanupHandles() [/usr/local/bin/node]
18: 0x10003df13 node::Environment::RunCleanup() [/usr/local/bin/node]
19: 0x1000b6f8a node::NodeMainInstance::Run() [/usr/local/bin/node]
20: 0x10005fd0c node::Start(int, char**) [/usr/local/bin/node]
21: 0x7fff6dd953d5 start [/usr/lib/system/libdyld.dylib]
[1]    32869 abort      node --expose-gc test/asyncworker.js

I would expect:

  1. the code to remain working as is, or
  2. any async code to not be called anymore and the process to exit cleanly, or
  3. (edit) a user-friendly JS exception to be thrown

I am running into this issue with zeromq.js, where I can't control users calling into the native code from an exit hook and this leads to similar fatal errors. Maybe there could be a way to check if the process/thread is exiting from within the native code?

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions