Skip to content

Possible ExitEvent leak when BlockingCall() returns napi_closing #938

Description

@OvOhao

Possible ExitEvent leak when BlockingCall() returns napi_closing

I found a possible native heap leak in the pty exit watcher when the thread-safe function is closing.

Files: src/unix/pty.cc, src/win/conpty.cc

Function: SetupExitCallback

Relevant Unix code:

auto callback = [](Napi::Env env, Napi::Function cb, ExitEvent *exit_event) {
  cb.Call({Napi::Number::New(env, exit_event->exit_code),
           Napi::Number::New(env, exit_event->signal_code)});
  delete exit_event;
};

// ...

ExitEvent *exit_event = new ExitEvent;
// fill exit_event
auto status = tsfn.BlockingCall(exit_event, callback);
switch (status) {
  case napi_closing:
    break;

  case napi_queue_full:
    Napi::Error::Fatal("SetupExitCallback", "Queue was full");

  case napi_ok:
    if (tsfn.Release() != napi_ok) {
      Napi::Error::Fatal("SetupExitCallback", "ThreadSafeFunction.Release() failed");
    }
    break;
}

The Windows implementation has the same ownership pattern.

The ExitEvent is deleted only by the JS-thread callback. If BlockingCall()
returns napi_closing, the item was not queued and that callback will not run.
The case napi_closing: branch then drops the only pointer to exit_event.

This is a small teardown-race leak: one ExitEvent per pty whose exit races
Node-API environment shutdown.

Suggested fix: delete exit_event in the napi_closing branch, where ownership
was not transferred to the callback queue.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions