Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

TLSWRAP open handle wrongly detected with MongoDB connections #11665

Closed
lblanch opened this issue Jul 15, 2021 · 24 comments · Fixed by #13414
Closed

TLSWRAP open handle wrongly detected with MongoDB connections #11665

lblanch opened this issue Jul 15, 2021 · 24 comments · Fixed by #13414

Comments

@lblanch
Copy link

lblanch commented Jul 15, 2021

💥 Regression Report

Running Jest with --detectOpenHandles detects a TLSWRAP potential open handle on MongoDb connections, even if they are properly closed in an "afterAll" enclosure and jest exits without issues. The exact message is:

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

  ●  TLSWRAP

       6 |
       7 | beforeAll(async () => {
    >  8 |   await client.connect()
         |                ^
       9 | })
      10 |
      11 | afterAll(async () => {

      at Object.resolveSRVRecord (node_modules/mongodb/src/connection_string.ts:69:7)
      at Object.connect (node_modules/mongodb/src/operations/connect.ts:51:12)
      at node_modules/mongodb/src/mongo_client.ts:410:7
      at Object.maybePromise (node_modules/mongodb/src/utils.ts:618:3)
      at MongoClient.connect (node_modules/mongodb/src/mongo_client.ts:409:12)
      at app.test.js:8:16
      at TestScheduler.scheduleTests (node_modules/@jest/core/build/TestScheduler.js:333:13)
      at runJest (node_modules/@jest/core/build/runJest.js:387:19)
      at _run10000 (node_modules/@jest/core/build/cli/index.js:408:7)
      at runCLI (node_modules/@jest/core/build/cli/index.js:261:3)

This seems to happen with MongoDb's official NodeJs driver, and, by extension, any other packages that use that driver (in my case I got it with mongoose and connect-mongo-session)

Last working version

Worked up to version: 26.6.3

Stopped working in version: 27.0.0

To Reproduce

NOTE: A valid MongoDB URI is needed to reproduce this issue.

Within an empty directory:

  1. Create a new empty Node project with:
npm init 
  1. Install jest and mongodb
npm install jest mongodb
  1. Create a app.test.js file with following contents (add a valid MongoDB URI where necessary):
const { MongoClient } = require("mongodb");

// Replace the uri string with your MongoDB connection string.
const uri ='YOUR-MONGODB-URI'
const client = new MongoClient(uri)

beforeAll(async () => {
  await client.connect()
})

afterAll(async () => {
  await client.close()
})

test('testing', () => {
  expect(client).toBeDefined()
})
  1. Run the test with jest and --detectOpenHandles
jest --detectOpenHandles

Expected behavior

No open handles should be found.

Link to repl or repo (highly encouraged)

Use the code snippet provided above.

Run npx envinfo --preset jest

Paste the results here:

System:
    OS: macOS 11.4
    CPU: (8) arm64 Apple M1
  Binaries:
    Node: 16.4.2 - /opt/local/bin/node
    npm: 7.19.1 - /opt/local/bin/npm
  npmPackages:
    jest: ^27.0.0 => 27.0.6 

Thank you for your help! <3

@dimaip
Copy link

dimaip commented Jul 20, 2021

I can confirm having exactly the same issue with Jest 27.0.4.

@clinzy25
Copy link

I'm having the same issue as well

@Eitz
Copy link

Eitz commented Sep 23, 2021

I was having the same problem and blaming Jest or the mongodb node driver.

The reason for the problem was that I wasn't actually dealing with a singleton like I was lead to believe: even though I was following the text-book standard for connection pooling in an application, each file ran by Jest was creating a new connection when doing an import of my getConnection function that was supposed to access the singleton stored in a module-level variable. I am not sure why Jest interpreted N-times my db.js file, but it did (while N=number of test files).

I suggest to verify if the same problem is happening to you; my solution so far has been to add an afterAll with db.close() in my topmost describe blocks in each test file. The afterAll closes each connection opened (same as I was doing in the jest-teardown level).

Now my jest runs close without forcing and no open handles are left.

@sschmeck
Copy link

I detected a similar issue using the ioredis package within the beforeEach function.

// src/helper/redis.ts
async function withRedis(handle: (r: IORedis.Redis) => Promise<void>): Promise<void> {
  const redis = new Redis(Number(process.env.REDIS_PORT), process.env.REDIS_HOSTNAME, {
    password: process.env.REDIS_PASSWORD,
    tls: true as any,
  });

  try { 
    await handle(redis);
  } finally {
    await redis.quit();
  }
}

// src/some.spec.ts
beforeEach(async () => {
  await withRedis(async redis => {
    const keys = await redis.keys(`*${process.env.KEY_PREFIX}*`);
    for (const key of keys) {
      await redis.del(key);
    }
  });
});

It produces sporadically the following warning with --detectOpenHandles.

Jest has detected the following 2 open handles potentially keeping Jest from exiting:

  ●  TLSWRAP

      13 |
      14 | async function withRedis(handle: (r: IORedis.Redis) => Promise<void>): Promise<void> {
    > 15 |   const redis = new Redis(Number(process.env.REDIS_PORT), process.env.REDIS_HOSTNAME, {
         |                 ^
      16 |     password: process.env.REDIS_PASSWORD,
      17 |     tls: true as any,
      18 |   });

      at node_modules/ioredis/built/connectors/StandaloneConnector.js:48:21
      at StandaloneConnector.connect (node_modules/ioredis/built/connectors/StandaloneConnector.js:47:16)
      at node_modules/ioredis/built/redis/index.js:272:55
      at Redis.Object.<anonymous>.Redis.connect (node_modules/ioredis/built/redis/index.js:251:21)
      at new Redis (node_modules/ioredis/built/redis/index.js:160:14)
      at withRedis (src/helper/redis.ts:15:17)
      at Object.<anonymous> (src/some.spec.ts:22:11)

@vollmerr
Copy link

I have the same issue with something as simple as this using axios

import axios from "axios";

it("runs without open handles", async () => {
  await axios.get("https://jsonplaceholder.typicode.com/todos/1");
  expect(true).toBe(true);
});

"axios": "^0.21.4",
"jest": "^27.2.4",
"ts-jest": "^27.0.5"

@malthe
Copy link

malthe commented Nov 5, 2021

The problem seems to be that the Async hooks module in Node doesn't emit a destroy event for the "TLSWRAP" hook type.

In Jest we have today something like:

if (type === 'PROMISE' ||
    type === 'TIMERWRAP' ||
    type === 'ELDHISTOGRAM' ||
    type === 'PerformanceObserver' ||
    type === 'RANDOMBYTESREQUEST' ||
    type === 'DNSCHANNEL' ||
    type === 'ZLIB'
  ) return;

These are all problematic hook types that are simply ignored. And "TLSWRAP" should probably be added to this list.

(Since there is always an underlying socket object which is what we're really interested in.)

But to test this, we'd have to set up a TLS session object which is not exactly trivial.

@dornfeder
Copy link

Any news on this? Stumbled over this problem just now and desperately trying to find a solution for it...

@malthe
Copy link

malthe commented Nov 30, 2021

@dornfeder I have success with simply adding a test in the snippet above for "TLSWRAP". But to test that out is a lot of CI/CD work setting up a certificate, etc.

@odziem
Copy link

odziem commented Mar 21, 2022

Great catch @malthe! I can confirm that adding TLSWRAP to the ignore list in collectHandles seems to get rid of the issue for MongoDB connections. Would be nice to get some eyes on this.

@valerii15298
Copy link

Great catch @malthe! I can confirm that adding TLSWRAP to the ignore list in collectHandles seems to get rid of the issue for MongoDB connections. Would be nice to get some eyes on this.

@odziem
Can you please explain how to do that? I did not find it in jest documentation and still wanna fix the problem.

@ruthcyg
Copy link

ruthcyg commented Jun 11, 2022

I face similar error it was solve by removing --detectOpenHandles,

@malthe
Copy link

malthe commented Jun 11, 2022

@ruthcyg but that is arguably the wrong solution.

@mkovel
Copy link

mkovel commented Jul 12, 2022

Guys any updates here?

@prma85
Copy link

prma85 commented Jul 18, 2022

Having the same issue with Axios

@myothuko98
Copy link

still have same issue here.

any updates?

@SimenB
Copy link
Member

SimenB commented Aug 8, 2022

The problem seems to be that the Async hooks module in Node doesn't emit a destroy event for the "TLSWRAP" hook type.

@malthe would you say that's a bug in node? Or should we just ignore the type?

@malthe
Copy link

malthe commented Aug 8, 2022

@SimenB it's some time since I started the thread, but I think it's just a bug in jest really:

And "TLSWRAP" should probably be added to this list.

@UriConMur
Copy link

Im having same issues, there is nothing else I can do to awai the mongoose.connect or mongoDB.connect async.
Plese let us know if there is a workaround.

@aronilie
Copy link

aronilie commented Aug 21, 2022

I'm having the same problem while doing:

const user = {
  userName: "lionardo",
  password: "freeSabana",
};

describe("Given the route /register", () => {
  describe("When the method is POST", () => {
    test("Then a valid endpoint should have been sent a 201 code and registered a user", async () => {
      await User.deleteMany();
      const response = await request(app).post("/users/register").send(user);
      expect(response.statusCode).toEqual(201);
    });
  });
});

The error is:

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

● TCPWRAP

  23 |     });
  24 |
> 25 |     mongoose.connect(mongoUrl, (error) => {
     |              ^
  26 |       if (error) {
  27 |         debug(
  28 |           chalk.bgRed.white(`

I have tried so many things but without any result.

I think the problem is that database connection isn't really closed. I tried:
if (process.env.NODE_ENV === 'test') { mongoose.connection.close(); }); }

For some reason NODE_ENV is dev, despite that the documentation states that it's expected to be test.

Closing database connection immediately on application start may cause problems in units that actually use database connection. As explained in the guide, MongoDB connection should be at the end of test. Since default Mongoose connection is used, it can be:

afterAll(() => mongoose.disconnect());

Anything of those worked for me, but I think the solution is close. If anyone see anything with that please let us know.

@roblframpton
Copy link

I am also experiencing this issue, but for me it is caused by node-fetch. As far as I can see none of the workarounds in this thread are applicable, and I don't think I'm doing anything wrong, e.g. missing an await. Is there any way I can get my tests to pass?

@pimterry
Copy link
Contributor

pimterry commented Oct 7, 2022

I have a much simpler reproduction for this issue:

const tls = require('tls');

describe('Test', () => {
  it("runs a test", () => {
      const socket = new tls.TLSSocket();
      socket.destroy();
  });
});

Prints:

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

  ●  TLSWRAP

      3 | describe('Test', () => {
      4 |   it("runs a test", () => {
    > 5 |       const socket = new tls.TLSSocket();
        |                      ^
      6 |       socket.destroy();
      7 |   });
      8 | });

      at Object.<anonymous> (index.test.ts:5:22)

@liuxingbaoyu
Copy link
Contributor

liuxingbaoyu commented Oct 7, 2022

node --expose-gc .\node_modules\jest-cli\bin\jest.js

const tls = require("tls");
describe("Test", () => {
  it("runs a test", async () => {
    let socket = new tls.TLSSocket();
    socket.destroy();

    await new Promise(r => setTimeout(r, 0));
    // @ts-ignore
    gc();
  });
});

This seems to do the trick, but I'm not sure how I should open the PR in jest. Looks like it's better left to you to decide.
cc @SimenB

@SimenB
Copy link
Member

SimenB commented Oct 14, 2022

@github-actions
Copy link

This issue has been automatically locked since there has not been any recent activity after it was closed. Please open a new issue for related bugs.
Please note this issue tracker is not a help forum. We recommend using StackOverflow or our discord channel for questions.

@github-actions github-actions bot locked as resolved and limited conversation to collaborators Nov 14, 2022
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging a pull request may close this issue.