Skip to content

Jest and why-is-node-running reports open handle on exit due to InstanceData.drop_queue's threadsafe function #948

@mjameswh

Description

@mjameswh

Description

In Napi-6 mode, InstanceData registers a drop_queue threadsafe function, which ensures that leaked resources correctly get disposed. However, that TSFN itself is being reported by Jest, why-is-node-running and others (simply "Jest" thereafter) as leaked resources, since that function is never destroyed.

How to reproduce

InstanceData is lazily initialized. The issue can be observed by running any Jest test that forces initialization of InstanceData, notably by creating a Root object or calling cx.channel(). For example:

In rust:

pub fn test_inner_data_drop_queue_leak(mut cx: FunctionContext) -> JsResult<JsObject> {
    let some_object = cx.empty_object().root(&mut cx).into_inner(&mut cx);
    Ok(some_object)
}

In JavaScript:

test('httpWorkflow with mock activity', () => {
      (native as any).testInnerDataDropQueueLeak();
});

Then running using:

npx jest --detectOpenHandles

Results in this Jest warning message:

Jest has detected the following 1 open handle potentially keeping Jest from exiting:

  ●  neon threadsafe function

Discussion and Proposed solution

This is technically a false-positive, since the TSFN is .unref() so that it will not keep the process alive. Unfortunately, Node's async hook callbacks API doesn't provide an official way to know that an async resource has been unref(). There is therefore no way for Jest to know that the TSFN can safely be ignored, by relying on Node's API alone.

Instead, Jest looks at the async resource for the presence of a hasRef() function. If that function exists on the async resource object, then Jest will call that function to determine if the resource is still active. why-is-node-running also support that function, and Node itself has added this function on some of its own resources specifically for the purpose of helping Jest and the like properly detect leaks.

It is therefore suggested that a hasRef() function should be added to the async resource associated TSFNs. That function would simply return either true or false, depending on either the TSFN is referenced() or unref().

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