Skip to content

Commit 5d95fff

Browse files
cegofrhsclaudefirecow
authored
Fix Docker volume leak on SIGTERM, SIGHUP, and concurrent cleanup (#1787)
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> Co-authored-by: Mads Jon Nielsen <madsjon@gmail.com>
1 parent 363953d commit 5d95fff

2 files changed

Lines changed: 24 additions & 7 deletions

File tree

src/index.ts

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,23 @@ import packageJson from "../package.json";
1414

1515
const jobs: Job[] = [];
1616

17-
process.on("SIGINT", async (_: string, code: number) => {
18-
await cleanupJobResources(jobs);
19-
process.exit(code);
20-
});
17+
let cleanupAndExitPromise: Promise<void> | null = null;
18+
19+
async function cleanupAndExit (code: number) {
20+
// First caller's exit code wins — subsequent callers join the in-flight cleanup.
21+
if (cleanupAndExitPromise) return cleanupAndExitPromise;
22+
cleanupAndExitPromise = cleanupJobResources(jobs).finally(() => process.exit(code));
23+
return cleanupAndExitPromise;
24+
}
25+
26+
process.on("SIGINT", () => cleanupAndExit(130));
27+
process.on("SIGTERM", () => cleanupAndExit(143));
28+
process.on("SIGHUP", () => cleanupAndExit(129));
2129

2230
// Graceful shutdown for nodemon
23-
process.on("SIGUSR2", async () => await cleanupJobResources(jobs));
31+
process.on("SIGUSR2", async () => {
32+
if (!cleanupAndExitPromise) await cleanupJobResources(jobs);
33+
});
2434

2535
(() => {
2636
const yparser = yargs(process.argv.slice(2));
@@ -42,8 +52,7 @@ process.on("SIGUSR2", async () => await cleanupJobResources(jobs));
4252
} else {
4353
process.stderr.write(chalk`{red ${e.stack ?? e}}\n`);
4454
}
45-
await cleanupJobResources(jobs);
46-
process.exit(1);
55+
await cleanupAndExit(1);
4756
}
4857
},
4958
builder: (y: any) => {

src/job.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -772,11 +772,19 @@ If you know what you're doing and would like to suppress this warning, use one o
772772
}
773773
}
774774

775+
private _cleanupPromise: Promise<void> | null = null;
776+
775777
async cleanupResources () {
776778
clearTimeout(this._longRunningSilentTimeout);
777779

778780
if (!this.argv.cleanup) return;
779781

782+
if (this._cleanupPromise) return this._cleanupPromise;
783+
this._cleanupPromise = this._doCleanupResources();
784+
return this._cleanupPromise;
785+
}
786+
787+
private async _doCleanupResources () {
780788
if (this._containersToClean.length > 0) {
781789
try {
782790
await Utils.spawn([this.argv.containerExecutable, "rm", "-vf", ...this._containersToClean]);

0 commit comments

Comments
 (0)