Ask
116

vitest 3 finishes green then hangs for 10s and warns about closing Vitest

340 tests, 6.2 seconds of actual test time, all passing. Then the process sits there doing nothing, eventually complains that closing timed out and that something is probably keeping the process alive, and exits.

In CI the job takes 90 seconds because of the hang, and roughly one run in ten never exits at all and gets killed at the six minute job timeout.

--pool=forks did not change anything. Nothing in the tests opens a connection - the database layer is mocked.

9 answers Share
Report

Answering anonymously — a moderator will review it first.

  • @thermal_theo · 2w ago

    For the things you open on purpose, use the teardown hooks rather than hoping. globalSetup can return a function that runs at the very end - that is where servers and pools you started deliberately get closed. Anything per-file goes in afterAll in a setup file.

    The rule I use: if a test file imports it and it has a close() or end(), something in that file is responsible for calling it.

    79
    Share
    Reply

    Answering anonymously — a moderator will review it first.

    Report
  • @merino_max · 2w ago · 3 replies

    "The database is mocked" and "nothing opens a connection" are different claims, and the gap between them is where your handle is.

    Something at module scope opens something at import time, before any mock is relevant. The usual suspects:

    • a new Pool() exported from a module you import for one type
    • a redis or queue client constructed at the top of a config file
    • setInterval in a cache, rate limiter or metrics module
    • a tracing or error SDK that starts on import

    Stop guessing and ask node:

    node --import why-is-node-running/register ./node_modules/vitest/vitest.mjs run
    

    It prints every handle still holding the loop open with the stack that created it. Nine times out of ten it is one line in one file, and it is never the file you suspected.

    149
    Share
    Reply

    Answering anonymously — a moderator will review it first.

    Report
    • @sawdust_pete · last wk.

      It was a setInterval in our rate limiter. .unref() on the timer and the suite has exited cleanly for a year. One method call.

      47
      Share
      Reply

      Answering anonymously — a moderator will review it first.

      Report
    • @overlap_owen · 2w ago

      Same class of thing here - an error reporting SDK initialised in a shared config import. Guarded it behind an env check and the hang vanished.

      21
      Share
      Reply

      Answering anonymously — a moderator will review it first.

      Report
  • @row_level_sam · 2w ago

    The ten seconds is teardownTimeout, which defaults to 10000. People's first instinct is to raise it. Do not - it is the smoke alarm, not the fire. If you raise it to 30s you have simply agreed to wait longer for the same problem.

    54
    Share
    Reply

    Answering anonymously — a moderator will review it first.

    Report
  • @fg_stall · 2w ago

    --pool=forks with isolation on is slower but the leak dies with the fork, so at least the one-in-ten total hang stops while you hunt. Treat it as a tourniquet, not a fix.

    24
    Share
    Reply

    Answering anonymously — a moderator will review it first.

    Report
  • @shopvac_ghost · 2w ago · 2 replies

    Custom reporter with process.exit(0) at the end. Tests passed, who cares what handles are open.

    22
    Share
    Reply

    Answering anonymously — a moderator will review it first.

    Report
    • @wick_and_wax · 2w ago

      It works until the run where you exit before the coverage reporter flushes, and your coverage gate fails at 0% for no reason anyone can explain. Or before the JUnit XML is written and CI reports "no tests found" on a green run. Both of those cost more time than the leak.

      It also throws away the actual signal, which is that some module in your production code holds a handle it never releases. That is a real property of your app, not a test framework quirk.

      15
      Share
      Reply

      Answering anonymously — a moderator will review it first.

      Report
  • @adjunct_life · last wk.

    Check for a request mocking server started in setup and never closed. That one bit me and it is invisible because the tests all pass.

    7
    Share
    Reply

    Answering anonymously — a moderator will review it first.

    Report