temporalio / samples-typescript

Other
317 stars 122 forks source link

[Bug] Jest detects open handle #189

Open lorensr opened 2 years ago

lorensr commented 2 years ago
cd activities-examples
npm run jest -- -t httpWorkflow

results in:

A worker process has failed to exit gracefully and has been force exited. This is likely caused by tests leaking due to improper teardown. Try running with --detectOpenHandles to find leaks. Active timers can also cause this, ensure that .unref() was called on them.

npm run jest -- -t httpWorkflow --detectOpenHandles

results in:

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

  ●  neon threadsafe function

    > 1 | import { TestWorkflowEnvironment } from '@temporalio/testing';
        | ^
      2 | import { Worker, Runtime, DefaultLogger, LogEntry } from '@temporalio/worker';
      3 | import { v4 as uuid4 } from 'uuid';
      4 | import { httpWorkflow } from './workflows';

      at Object.<anonymous> (node_modules/@temporalio/worker/src/worker.ts:95:8)
      at Object.<anonymous> (node_modules/@temporalio/worker/src/index.ts:29:1)
      at Object.<anonymous> (node_modules/@temporalio/testing/src/index.ts:20:1)
      at Object.<anonymous> (src/workflows.test.ts:1:1)
vkarpov15 commented 2 years ago

I did a bunch of digging on this. Haven't been able to make much headway, however my best guess is that Jest is preventing something in Worker package from cleaning up properly.

Below is the info on the only entry in process._getActiveHandles() in afterAll() that isn't stdio.

<ref *1> ReadStream {
  connecting: false,
  _hadError: false,
  _parent: null,
  _host: null,
  _readableState: ReadableState {
    objectMode: false,
    highWaterMark: 0,
    buffer: BufferList { head: null, tail: null, length: 0 },
    length: 0,
    pipes: [],
    flowing: null,
    ended: false,
    endEmitted: false,
    reading: false,
    constructed: true,
    sync: false,
    needReadable: true,
    emittedReadable: false,
    readableListening: false,
    resumeScheduled: false,
    errorEmitted: false,
    emitClose: false,
    autoDestroy: true,
    destroyed: false,
    errored: null,
    closed: false,
    closeEmitted: false,
    defaultEncoding: 'utf8',
    awaitDrainWriters: null,
    multiAwaitDrain: false,
    readingMore: false,
    dataEmitted: false,
    decoder: null,
    encoding: null,
    [Symbol(kPaused)]: null
  },
  _events: [Object: null prototype] {
    end: [Function: onReadableStreamEnd],
    pause: [Function (anonymous)]
  },
  _eventsCount: 2,
  _maxListeners: undefined,
  _writableState: WritableState {
    objectMode: false,
    highWaterMark: 0,
    finalCalled: false,
    needDrain: false,
    ending: false,
    ended: false,
    finished: false,
    destroyed: false,
    decodeStrings: false,
    defaultEncoding: 'utf8',
    length: 0,
    writing: false,
    corked: 0,
    sync: true,
    bufferProcessing: false,
    onwrite: [Function: bound onwrite],
    writecb: null,
    writelen: 0,
    afterWriteTickInfo: null,
    buffered: [],
    bufferedIndex: 0,
    allBuffers: true,
    allNoop: true,
    pendingcb: 0,
    constructed: true,
    prefinished: false,
    errorEmitted: false,
    emitClose: false,
    autoDestroy: true,
    errored: null,
    closed: false,
    closeEmitted: false,
    writable: false,
    [Symbol(kOnFinished)]: []
  },
  allowHalfOpen: false,
  _sockname: null,
  _pendingData: null,
  _pendingEncoding: '',
  server: null,
  _server: null,
  isRaw: false,
  isTTY: true,
  fd: 0,
  [Symbol(async_id_symbol)]: 355,
  [Symbol(kHandle)]: TTY { reading: false, [Symbol(owner_symbol)]: [Circular *1] },
  [Symbol(kSetNoDelay)]: false,
  [Symbol(lastWriteQueueSize)]: 0,
  [Symbol(timeout)]: null,
  [Symbol(kBuffer)]: null,
  [Symbol(kBufferCb)]: null,
  [Symbol(kBufferGen)]: null,
  [Symbol(kCapture)]: false,
  [Symbol(kBytesRead)]: 0,
  [Symbol(kBytesWritten)]: 0
}

Below is the "neon threadsafe function" entries that pop up from why-is-node-running.

# neon threadsafe function
node:internal/async_hooks:201                                                                                         
/home/val/Workspace/temporalio/samples-typescript/activities-examples/node_modules/@temporalio/worker/lib/worker.js:65 - native.registerErrors(errors);
/home/val/Workspace/temporalio/samples-typescript/activities-examples/node_modules/jest-runtime/build/index.js:1714    - compiledFunction.call(
/home/val/Workspace/temporalio/samples-typescript/activities-examples/node_modules/jest-runtime/build/index.js:1223    - this._execModule(localModule, options, moduleRegistry, fromPath);
/home/val/Workspace/temporalio/samples-typescript/activities-examples/node_modules/jest-runtime/build/index.js:1047    - this._loadModule(
/home/val/Workspace/temporalio/samples-typescript/activities-examples/node_modules/jest-runtime/build/index.js:1248    - return this.requireModule(from, moduleName);
/home/val/Workspace/temporalio/samples-typescript/activities-examples/node_modules/@temporalio/worker/lib/index.js:38  - var worker_1 = require("./worker");
/home/val/Workspace/temporalio/samples-typescript/activities-examples/node_modules/jest-runtime/build/index.js:1714    - compiledFunction.call(
/home/val/Workspace/temporalio/samples-typescript/activities-examples/node_modules/jest-runtime/build/index.js:1223    - this._execModule(localModule, options, moduleRegistry, fromPath);

# neon threadsafe function
node:internal/async_hooks:201                                                                                         
/home/val/Workspace/temporalio/samples-typescript/activities-examples/node_modules/@temporalio/worker/lib/worker.js:65 - native.registerErrors(errors);
/home/val/Workspace/temporalio/samples-typescript/activities-examples/node_modules/jest-runtime/build/index.js:1714    - compiledFunction.call(
/home/val/Workspace/temporalio/samples-typescript/activities-examples/node_modules/jest-runtime/build/index.js:1223    - this._execModule(localModule, options, moduleRegistry, fromPath);
/home/val/Workspace/temporalio/samples-typescript/activities-examples/node_modules/jest-runtime/build/index.js:1047    - this._loadModule(
/home/val/Workspace/temporalio/samples-typescript/activities-examples/node_modules/jest-runtime/build/index.js:1248    - return this.requireModule(from, moduleName);
/home/val/Workspace/temporalio/samples-typescript/activities-examples/node_modules/@temporalio/worker/lib/index.js:38  - var worker_1 = require("./worker");
/home/val/Workspace/temporalio/samples-typescript/activities-examples/node_modules/jest-runtime/build/index.js:1714    - compiledFunction.call(
/home/val/Workspace/temporalio/samples-typescript/activities-examples/node_modules/jest-runtime/build/index.js:1223    - this._execModule(localModule, options, moduleRegistry, fromPath);

Neither is especially helpful from what I can tell, but maybe this helps someone else.

axelyung commented 2 years ago

Has there been any further progress on this?

vkarpov15 commented 2 years ago

I noticed that there's also a lag at the end of Mocha tests, so I suspect this problem isn't limited to just Jest. However, Mocha exits fine, it just takes a while. I can confirm the same thing in Jest: if I add a sufficiently high timeout (about 9200 on my machine), Jest exits without error:

afterAll(async () => {
  // Reduce this to 9100 and I still get the open handles issue. But at 9200, the open handles issue goes away
  await new Promise(resolve => setTimeout(resolve, 9200));
});

With Mocha, I get a similar issue: the test process hangs for approximately 9.1 seconds before exiting.

@bergundy is there anything you can think of in the test environment that has a 9 or 10 second timeout?

bergundy commented 2 years ago

I can’t think of anything with that timeout, might be something that happens internally in core. I think the old test environment didn’t have this issue, I’ll dig into this.

Thanks for looking at it, @vkarpov15

john-griffin commented 2 years ago

I also just hit this. Ported the example workflow test https://github.com/temporalio/samples-typescript/blob/a0574c6e27bf58b36bcc5f983e8a0941b412c606/activities-examples/src/workflows.test.ts to my app and I now see the following when running Jest:

jest --detectOpenHandles
...
Jest has detected the following 1 open handle potentially keeping Jest from exiting:

  ●  neon threadsafe function

    > 1 | import { TestWorkflowEnvironment } from '@temporalio/testing';