Skip to content

@swc/cli: swcDir() leaks a Piscina worker pool per invocation when used as a library #126

Open

Description

initialCompilation() in packages/cli/src/swc/dir.ts constructs a new Piscina({...}) and never destroys it. Harmless for one-shot CLI runs (the process exits), but swcDir() is also consumed as a library by long-lived processes — @nestjs/cli's swc builder re-invokes it on every file-add event in watch mode.

Each leaked pool permanently pins minThreads (= cores / 2) eagerly-spawned idle workers — piscina's default idleTimeout never reaps below the minimum. Observed in a week-long nest start --watch -b swc session on macOS:

  1. ~1,700 idle WorkerThreads, ~15 GB physical footprint (vmmap: dominated by tens of thousands of idle V8 isolate regions), one kqueue + one directory fd each (lsof);
  2. inspector NodeWorker enumeration showed every worker parked at piscina/dist/worker.js;
  3. thread count grows monotonically with src file-add events (renames and git checkouts count — atomic writes surface as add).

Repro without nest:

const swcDir = require("@swc/cli/lib/swc/dir").default;

(async () => {
  for (let i = 0; i < 10; i++) {
    await swcDir({
      cliOptions: {
        filenames: ["src"], outDir: "dist", sync: false, watch: false,
        extensions: [".ts"], copyFiles: false, includeDotfiles: false,
        stripLeadingPaths: false, quiet: true,
      },
      swcOptions: {},
    });
    // thread count grows by ~cores/2 per iteration:
    //   macOS: ps -M <pid> | wc -l      Linux: ls /proc/<pid>/task | wc -l
  }
})();

The watch path compiles via dirWorker directly and never uses this pool, so destroying it after the initial batch (e.g. .finally(() => workers.destroy())) is behavior-neutral.


This issue was investigated and drafted by Claude; I'm following up and available for a PR if wanted.

Activity

  1. kdy1 commented on Jul 29, 2026

    @kdy1
    Member

    Can you send a PR?

  2. higuaifan commented on Jul 29, 2026

    @higuaifan
    Author

    Sent: #130

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

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