Skip to content

Commit e124d3d

Browse files
committed
add tmp cleaner
1 parent 69a5e5b commit e124d3d

File tree

4 files changed

+140
-21
lines changed

4 files changed

+140
-21
lines changed

apps/coordinator/src/checkpointer.ts

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@ import { nanoid } from "nanoid";
55
import fs from "node:fs/promises";
66
import { ChaosMonkey } from "./chaosMonkey";
77
import { Buildah, Crictl, Exec } from "./exec";
8+
import { setTimeout } from "node:timers/promises";
9+
import { TempFileCleaner } from "./cleaner";
10+
import { numFromEnv, boolFromEnv } from "./util";
811

912
type CheckpointerInitializeReturn = {
1013
canCheckpoint: boolean;
@@ -100,6 +103,7 @@ export class Checkpointer {
100103
private simulatePushFailureSeconds: number;
101104

102105
private chaosMonkey: ChaosMonkey;
106+
private tmpCleaner?: TempFileCleaner;
103107

104108
constructor(private opts: CheckpointerOptions) {
105109
this.#dockerMode = opts.dockerMode;
@@ -116,6 +120,19 @@ export class Checkpointer {
116120
this.simulatePushFailureSeconds = opts.simulatePushFailureSeconds ?? 300;
117121

118122
this.chaosMonkey = opts.chaosMonkey ?? new ChaosMonkey(!!process.env.CHAOS_MONKEY_ENABLED);
123+
124+
if (boolFromEnv("TMP_CLEANER_ENABLED", false)) {
125+
const pathsOverride = process.env.TMP_CLEANER_PATHS_OVERRIDE?.split(",") ?? [];
126+
127+
this.tmpCleaner = new TempFileCleaner({
128+
paths: pathsOverride.length ? pathsOverride : [Buildah.tmpDir],
129+
maxAgeMinutes: numFromEnv("TMP_CLEANER_MAX_AGE_MINUTES", 60),
130+
intervalSeconds: numFromEnv("TMP_CLEANER_INTERVAL_SECONDS", 300),
131+
leadingEdge: boolFromEnv("TMP_CLEANER_LEADING_EDGE", false),
132+
});
133+
134+
this.tmpCleaner.start();
135+
}
119136
}
120137

121138
async init(): Promise<CheckpointerInitializeReturn> {
@@ -286,7 +303,7 @@ export class Checkpointer {
286303
});
287304

288305
this.#waitingForRetry.add(runId);
289-
await new Promise((resolve) => setTimeout(resolve, delay.milliseconds));
306+
await setTimeout(delay.milliseconds);
290307

291308
if (!this.#waitingForRetry.has(runId)) {
292309
this.#logger.log("Checkpoint canceled while waiting for retry", { runId });

apps/coordinator/src/cleaner.ts

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
import { SimpleLogger } from "@trigger.dev/core/v3/apps";
2+
import { Exec } from "./exec";
3+
import { setTimeout } from "timers/promises";
4+
5+
interface TempFileCleanerOptions {
6+
paths: string[];
7+
maxAgeMinutes: number;
8+
intervalSeconds: number;
9+
leadingEdge?: boolean;
10+
}
11+
12+
export class TempFileCleaner {
13+
private logger = new SimpleLogger("[tmp-cleaner]");
14+
private enabled = false;
15+
private exec = new Exec({ logger: this.logger });
16+
17+
constructor(private opts: TempFileCleanerOptions) {}
18+
19+
async start() {
20+
this.logger.log("start", this.opts);
21+
this.enabled = true;
22+
23+
if (!this.opts.leadingEdge) {
24+
await this.wait();
25+
}
26+
27+
while (this.enabled) {
28+
try {
29+
await this.clean();
30+
} catch (error) {
31+
this.logger.error("error during tick", error);
32+
}
33+
34+
await this.wait();
35+
}
36+
}
37+
38+
stop() {
39+
this.logger.log("stop", this.opts);
40+
this.enabled = false;
41+
}
42+
43+
private wait() {
44+
return setTimeout(this.opts.intervalSeconds * 1000);
45+
}
46+
47+
private async clean() {
48+
for (const path of this.opts.paths) {
49+
try {
50+
await this.cleanSingle(path);
51+
} catch (error) {
52+
this.logger.error("error while cleaning", { path, error });
53+
}
54+
}
55+
}
56+
57+
private async cleanSingle(startingPoint: string) {
58+
const maxAgeMinutes = this.opts.maxAgeMinutes;
59+
60+
const ignoreStartingPoint = ["!", "-path", startingPoint];
61+
const onlyDirectDecendants = ["-maxdepth", "1"];
62+
const onlyOldFiles = ["-mmin", `+${maxAgeMinutes}`];
63+
64+
const baseArgs = [
65+
startingPoint,
66+
...ignoreStartingPoint,
67+
...onlyDirectDecendants,
68+
...onlyOldFiles,
69+
];
70+
71+
const duArgs = ["-exec", "du", "-ch", "{}", "+"];
72+
const rmArgs = ["-exec", "rm", "-rf", "{}", "+"];
73+
74+
const du = this.x("find", [...baseArgs, ...duArgs]);
75+
const duOutput = await du;
76+
77+
const duLines = duOutput.stdout.trim().split("\n");
78+
const fileCount = duLines.length - 1; // last line is the total
79+
const fileSize = duLines.at(-1)?.split("\t")[0];
80+
81+
if (fileCount === 0) {
82+
this.logger.log("nothing to delete", { startingPoint, maxAgeMinutes });
83+
return;
84+
}
85+
86+
this.logger.log("deleting old files", { fileCount, fileSize, startingPoint, maxAgeMinutes });
87+
88+
const rm = this.x("find", [...baseArgs, ...rmArgs]);
89+
const rmOutput = await rm;
90+
91+
if (rmOutput.stderr.length > 0) {
92+
this.logger.error("delete unsuccessful", rmOutput);
93+
return;
94+
}
95+
96+
this.logger.log("deleted old files", { fileCount, fileSize, startingPoint, maxAgeMinutes });
97+
}
98+
99+
private get x() {
100+
return this.exec.x.bind(this.exec);
101+
}
102+
}

apps/coordinator/src/index.ts

Lines changed: 1 addition & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import { HttpReply, getTextBody } from "@trigger.dev/core/v3/apps";
1414
import { SimpleLogger } from "@trigger.dev/core/v3/apps";
1515
import { ChaosMonkey } from "./chaosMonkey";
1616
import { Checkpointer } from "./checkpointer";
17+
import { boolFromEnv, numFromEnv } from "./util";
1718

1819
import { collectDefaultMetrics, register, Gauge } from "prom-client";
1920
collectDefaultMetrics();
@@ -22,26 +23,6 @@ const HTTP_SERVER_PORT = Number(process.env.HTTP_SERVER_PORT || 8020);
2223
const NODE_NAME = process.env.NODE_NAME || "coordinator";
2324
const DEFAULT_RETRY_DELAY_THRESHOLD_IN_MS = 30_000;
2425

25-
const boolFromEnv = (env: string, defaultValue: boolean): boolean => {
26-
const value = process.env[env];
27-
28-
if (!value) {
29-
return defaultValue;
30-
}
31-
32-
return ["1", "true"].includes(value);
33-
};
34-
35-
const numFromEnv = (env: string, defaultValue: number): number => {
36-
const value = process.env[env];
37-
38-
if (!value) {
39-
return defaultValue;
40-
}
41-
42-
return parseInt(value, 10);
43-
};
44-
4526
const PLATFORM_ENABLED = ["1", "true"].includes(process.env.PLATFORM_ENABLED ?? "true");
4627
const PLATFORM_HOST = process.env.PLATFORM_HOST || "127.0.0.1";
4728
const PLATFORM_WS_PORT = process.env.PLATFORM_WS_PORT || 3030;

apps/coordinator/src/util.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
export const boolFromEnv = (env: string, defaultValue: boolean): boolean => {
2+
const value = process.env[env];
3+
4+
if (!value) {
5+
return defaultValue;
6+
}
7+
8+
return ["1", "true"].includes(value);
9+
};
10+
11+
export const numFromEnv = (env: string, defaultValue: number): number => {
12+
const value = process.env[env];
13+
14+
if (!value) {
15+
return defaultValue;
16+
}
17+
18+
return parseInt(value, 10);
19+
};

0 commit comments

Comments
 (0)