holdLock

tcmts.dev.holdLock · #qpeg2zjo · term · runs on the server

uses node:fs

Wait for lock (a file holding the pid of the process that has it), take it, and give it back when this process exits, or sooner, when what it returns is called. One left by a process that's gone is taken over. what says, while waiting, what's being waited for.

import fs from "node:fs";
import path from "node:path";

export async function holdLock(lock: string, what: string): Promise<() => void> {
  fs.mkdirSync(path.dirname(lock), { recursive: true });
  let said = false;
  for (;;) {
    try {
      fs.writeFileSync(lock, String(process.pid), { flag: "wx" });
      break;
    } catch (e) {
      if ((e as NodeJS.ErrnoException).code !== "EEXIST") throw e;
    }
    let pid = 0;
    try {
      pid = Number(fs.readFileSync(lock, "utf8").trim());
    } catch {
      continue;
    }
    let alive = false;
    try {
      process.kill(pid, 0);
      alive = pid > 0;
    } catch (e) {
      // (there, but another user's)
      alive = (e as NodeJS.ErrnoException).code === "EPERM";
    }
    if (!alive) {
      fs.rmSync(lock, { force: true });
      continue;
    }
    if (!said) console.log(`waiting for ${what} (pid ${pid}) to finish`);
    said = true;
    await new Promise((r) => setTimeout(r, 1000));
  }
  const release = () => {
    process.off("exit", release);
    try {
      if (fs.readFileSync(lock, "utf8").trim() === String(process.pid)) fs.rmSync(lock);
    } catch {}
  };
  process.on("exit", release);
  return release;
}

used by