| 
 | 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