Skip to content

Commit f320e14

Browse files
authored
cli: add dev lock file to prevent 2 dev processes running at the same time in the same dir (#1854)
* cli: add dev lock file to prevent 2 dev processes running at the same time in the same dir * Make sure the .trigger dir exists before creating the dev.lock file
1 parent 4d5080d commit f320e14

File tree

2 files changed

+97
-0
lines changed

2 files changed

+97
-0
lines changed

packages/cli-v3/src/commands/dev.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import { runtimeChecks } from "../utilities/runtimeCheck.js";
1111
import { getProjectClient, LoginResultOk } from "../utilities/session.js";
1212
import { login } from "./login.js";
1313
import { updateTriggerPackages } from "./update.js";
14+
import { createLockFile } from "../dev/lock.js";
1415

1516
const DevCommandOptions = CommonCommandOptions.extend({
1617
debugOtel: z.boolean().default(false),
@@ -120,6 +121,8 @@ async function startDev(options: StartDevOptions) {
120121
displayedUpdateMessage = await updateTriggerPackages(options.cwd, { ...options }, true, true);
121122
}
122123

124+
const removeLockFile = await createLockFile(options.cwd);
125+
123126
let devInstance: DevSessionInstance | undefined;
124127

125128
printDevBanner(displayedUpdateMessage);
@@ -178,6 +181,7 @@ async function startDev(options: StartDevOptions) {
178181
stop: async () => {
179182
devInstance?.stop();
180183
await watcher?.stop();
184+
removeLockFile();
181185
},
182186
waitUntilExit,
183187
};

packages/cli-v3/src/dev/lock.ts

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
import path from "node:path";
2+
import { readFile } from "../utilities/fileSystem.js";
3+
import { tryCatch } from "@trigger.dev/core/utils";
4+
import { logger } from "../utilities/logger.js";
5+
import { mkdir, writeFile } from "node:fs/promises";
6+
import { existsSync, unlinkSync } from "node:fs";
7+
import { onExit } from "signal-exit";
8+
9+
const LOCK_FILE_NAME = "dev.lock";
10+
11+
export async function createLockFile(cwd: string) {
12+
const currentPid = process.pid;
13+
const lockFilePath = path.join(cwd, ".trigger", LOCK_FILE_NAME);
14+
15+
logger.debug("Checking for lockfile", { lockFilePath, currentPid });
16+
17+
const removeLockFile = () => {
18+
try {
19+
logger.debug("Removing lockfile", { lockFilePath });
20+
return unlinkSync(lockFilePath);
21+
} catch (e) {
22+
// This sometimes fails on Windows with EBUSY
23+
}
24+
};
25+
const removeExitListener = onExit(removeLockFile);
26+
27+
const [, existingLockfileContents] = await tryCatch(readFile(lockFilePath));
28+
29+
if (existingLockfileContents) {
30+
// Read the pid number from the lockfile
31+
const existingPid = Number(existingLockfileContents);
32+
33+
logger.debug("Lockfile exists", { lockFilePath, existingPid, currentPid });
34+
35+
if (existingPid === currentPid) {
36+
logger.debug("Lockfile exists and is owned by current process", {
37+
lockFilePath,
38+
existingPid,
39+
currentPid,
40+
});
41+
42+
return () => {
43+
removeExitListener();
44+
removeLockFile();
45+
};
46+
}
47+
48+
// If the pid is different, try and kill the existing pid
49+
logger.debug("Lockfile exists and is owned by another process, killing it", {
50+
lockFilePath,
51+
existingPid,
52+
currentPid,
53+
});
54+
55+
try {
56+
process.kill(existingPid);
57+
// If it did kill the process, it will have exited, deleting the lockfile, so let's wait for that to happen
58+
// But let's not wait forever
59+
await new Promise((resolve, reject) => {
60+
const timeout = setTimeout(() => {
61+
clearInterval(interval);
62+
reject(new Error("Timed out waiting for lockfile to be deleted"));
63+
}, 5000);
64+
65+
const interval = setInterval(() => {
66+
if (!existsSync(lockFilePath)) {
67+
clearInterval(interval);
68+
clearTimeout(timeout);
69+
resolve(true);
70+
}
71+
}, 100);
72+
});
73+
} catch (error) {
74+
logger.debug("Failed to kill existing process, lets assume it's not running", { error });
75+
}
76+
}
77+
78+
// Now write the current pid to the lockfile
79+
await writeFileAndEnsureDirExists(lockFilePath, currentPid.toString());
80+
81+
logger.debug("Lockfile created", { lockFilePath, currentPid });
82+
83+
return () => {
84+
removeExitListener();
85+
removeLockFile();
86+
};
87+
}
88+
89+
async function writeFileAndEnsureDirExists(filePath: string, data: string) {
90+
const dir = path.dirname(filePath);
91+
await mkdir(dir, { recursive: true });
92+
await writeFile(filePath, data);
93+
}

0 commit comments

Comments
 (0)