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
This commit is contained in:
Eric Allam
2025-03-31 16:27:11 +01:00
committed by GitHub
parent 4d5080d097
commit f320e14f38
2 changed files with 97 additions and 0 deletions
+4
View File
@@ -11,6 +11,7 @@ import { runtimeChecks } from "../utilities/runtimeCheck.js";
import { getProjectClient, LoginResultOk } from "../utilities/session.js";
import { login } from "./login.js";
import { updateTriggerPackages } from "./update.js";
import { createLockFile } from "../dev/lock.js";
const DevCommandOptions = CommonCommandOptions.extend({
debugOtel: z.boolean().default(false),
@@ -120,6 +121,8 @@ async function startDev(options: StartDevOptions) {
displayedUpdateMessage = await updateTriggerPackages(options.cwd, { ...options }, true, true);
}
const removeLockFile = await createLockFile(options.cwd);
let devInstance: DevSessionInstance | undefined;
printDevBanner(displayedUpdateMessage);
@@ -178,6 +181,7 @@ async function startDev(options: StartDevOptions) {
stop: async () => {
devInstance?.stop();
await watcher?.stop();
removeLockFile();
},
waitUntilExit,
};
+93
View File
@@ -0,0 +1,93 @@
import path from "node:path";
import { readFile } from "../utilities/fileSystem.js";
import { tryCatch } from "@trigger.dev/core/utils";
import { logger } from "../utilities/logger.js";
import { mkdir, writeFile } from "node:fs/promises";
import { existsSync, unlinkSync } from "node:fs";
import { onExit } from "signal-exit";
const LOCK_FILE_NAME = "dev.lock";
export async function createLockFile(cwd: string) {
const currentPid = process.pid;
const lockFilePath = path.join(cwd, ".trigger", LOCK_FILE_NAME);
logger.debug("Checking for lockfile", { lockFilePath, currentPid });
const removeLockFile = () => {
try {
logger.debug("Removing lockfile", { lockFilePath });
return unlinkSync(lockFilePath);
} catch (e) {
// This sometimes fails on Windows with EBUSY
}
};
const removeExitListener = onExit(removeLockFile);
const [, existingLockfileContents] = await tryCatch(readFile(lockFilePath));
if (existingLockfileContents) {
// Read the pid number from the lockfile
const existingPid = Number(existingLockfileContents);
logger.debug("Lockfile exists", { lockFilePath, existingPid, currentPid });
if (existingPid === currentPid) {
logger.debug("Lockfile exists and is owned by current process", {
lockFilePath,
existingPid,
currentPid,
});
return () => {
removeExitListener();
removeLockFile();
};
}
// If the pid is different, try and kill the existing pid
logger.debug("Lockfile exists and is owned by another process, killing it", {
lockFilePath,
existingPid,
currentPid,
});
try {
process.kill(existingPid);
// If it did kill the process, it will have exited, deleting the lockfile, so let's wait for that to happen
// But let's not wait forever
await new Promise((resolve, reject) => {
const timeout = setTimeout(() => {
clearInterval(interval);
reject(new Error("Timed out waiting for lockfile to be deleted"));
}, 5000);
const interval = setInterval(() => {
if (!existsSync(lockFilePath)) {
clearInterval(interval);
clearTimeout(timeout);
resolve(true);
}
}, 100);
});
} catch (error) {
logger.debug("Failed to kill existing process, lets assume it's not running", { error });
}
}
// Now write the current pid to the lockfile
await writeFileAndEnsureDirExists(lockFilePath, currentPid.toString());
logger.debug("Lockfile created", { lockFilePath, currentPid });
return () => {
removeExitListener();
removeLockFile();
};
}
async function writeFileAndEnsureDirExists(filePath: string, data: string) {
const dir = path.dirname(filePath);
await mkdir(dir, { recursive: true });
await writeFile(filePath, data);
}