From 8d55ae324e2d8cf5b9f20358fb391412cd66de4e Mon Sep 17 00:00:00 2001 From: Eric Allam Date: Mon, 31 Mar 2025 12:08:30 +0100 Subject: [PATCH 1/2] cli: add dev lock file to prevent 2 dev processes running at the same time in the same dir --- packages/cli-v3/src/commands/dev.ts | 4 ++ packages/cli-v3/src/dev/lock.ts | 87 +++++++++++++++++++++++++++++ 2 files changed, 91 insertions(+) create mode 100644 packages/cli-v3/src/dev/lock.ts diff --git a/packages/cli-v3/src/commands/dev.ts b/packages/cli-v3/src/commands/dev.ts index b6c652d786..8634ad422d 100644 --- a/packages/cli-v3/src/commands/dev.ts +++ b/packages/cli-v3/src/commands/dev.ts @@ -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, }; diff --git a/packages/cli-v3/src/dev/lock.ts b/packages/cli-v3/src/dev/lock.ts new file mode 100644 index 0000000000..9fb1a14fd7 --- /dev/null +++ b/packages/cli-v3/src/dev/lock.ts @@ -0,0 +1,87 @@ +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 { 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 writeFile(lockFilePath, currentPid.toString()); + + logger.debug("Lockfile created", { lockFilePath, currentPid }); + + return () => { + removeExitListener(); + removeLockFile(); + }; +} From fb61d95bd924e71ed95e27d0b9c2e35747477db4 Mon Sep 17 00:00:00 2001 From: Eric Allam Date: Mon, 31 Mar 2025 16:08:33 +0100 Subject: [PATCH 2/2] Make sure the .trigger dir exists before creating the dev.lock file --- packages/cli-v3/src/dev/lock.ts | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/packages/cli-v3/src/dev/lock.ts b/packages/cli-v3/src/dev/lock.ts index 9fb1a14fd7..602607a4c0 100644 --- a/packages/cli-v3/src/dev/lock.ts +++ b/packages/cli-v3/src/dev/lock.ts @@ -2,7 +2,7 @@ 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 { writeFile } from "node:fs/promises"; +import { mkdir, writeFile } from "node:fs/promises"; import { existsSync, unlinkSync } from "node:fs"; import { onExit } from "signal-exit"; @@ -76,7 +76,7 @@ export async function createLockFile(cwd: string) { } // Now write the current pid to the lockfile - await writeFile(lockFilePath, currentPid.toString()); + await writeFileAndEnsureDirExists(lockFilePath, currentPid.toString()); logger.debug("Lockfile created", { lockFilePath, currentPid }); @@ -85,3 +85,9 @@ export async function createLockFile(cwd: string) { removeLockFile(); }; } + +async function writeFileAndEnsureDirExists(filePath: string, data: string) { + const dir = path.dirname(filePath); + await mkdir(dir, { recursive: true }); + await writeFile(filePath, data); +}