@swc/cli: swcDir() leaks a Piscina worker pool per invocation when used as a library
Nobody has claimed this yet.
Assessment
- Difficulty
- 2/5
- Estimated time
- 1-3 hours
- Newbie friendliness
- 75/100
- Issue type
- Bug
- Clarity
- Clearly specified
- Activity status
- Quiet
- Tech stack
- node.js, typescript
Research direction
Start in packages/cli/src/swc/dir.ts at initialCompilation(), then trace the swcDir() call path and Piscina worker lifecycle. Run the supplied repeated swcDir() reproduction and compare process thread counts before and after the initial batch. Done means repeated library invocations no longer leave worker pools behind, while the watch path remains behavior-neutral.
Written by the indexing model from the issue text.
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,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); - inspector
NodeWorkerenumeration showed every worker parked atpiscina/dist/worker.js; - 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.
- Dominant language
- TypeScript
- Stars
- 86
- Forks
- 44
- Avg merge
- 3d 49m
- Merged PRs (30d)
- 1
Contributor guide
No contributing guide indexed for this repository
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
More from swc-project/pkgs
-
discussion: esm-only Open
Difficulty 5/5 Over a week Newbie friendliness 25/100
swc-project/pkgs#132 ·
-
Difficulty 5/5 Over a week Newbie friendliness 35/100
swc-project/pkgs#131 ·
-
Difficulty 3/5 1-2 days Newbie friendliness 45/100
swc-project/pkgs#129 · 1 comment ·
-
Difficulty 3/5 1-2 days Newbie friendliness 42/100
swc-project/pkgs#112 · 1 reaction ·
-
swc-project/pkgs#97 · 12 comments · 1 assignee ·
All issues in swc-project/pkgs
Similar issues
-
calcite-components needs triage refactor
Difficulty 2/5 1-3 hours Newbie friendliness 75/100
Esri/calcite-design-system#15203 ·
-
Difficulty 2/5 1-3 hours Newbie friendliness 91/100
-
community first-timers-only good first issue hacktoberfest help wanted low hanging fruit up-for-grabs
Difficulty 1/5 Under an hour Newbie friendliness 95/100
-
Difficulty 2/5 1-3 hours Newbie friendliness 78/100
Automattic/studio#4908 ·
-
Difficulty 2/5 1-3 hours Newbie friendliness 90/100