Repository navigation
fix(cli): each start sweeps the running markers of CLIs that are gone #5790
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,4 +1,4 @@ | ||
| import { mkdirSync, unlinkSync, writeFileSync } from "node:fs"; | ||
| import { mkdirSync, readdirSync, statSync, unlinkSync, writeFileSync } from "node:fs"; | ||
| import { homedir } from "node:os"; | ||
| import { join } from "node:path"; | ||
|
|
||
|
|
@@ -7,6 +7,38 @@ export const RUNNING_DIR = join(homedir(), ".hyperframes", "running"); | |
| const RUNNING_HEARTBEAT_MS = 30_000; | ||
| export const RUNNING_STALE_MS = 10 * 60_000; | ||
|
|
||
| function isAlive(pid: number): boolean { | ||
| try { | ||
| process.kill(pid, 0); | ||
| return true; | ||
| } catch (error) { | ||
| // EPERM: the pid exists but belongs to another user. | ||
| return error instanceof Error && "code" in error && error.code === "EPERM"; | ||
| } | ||
| } | ||
|
|
||
| function pruneExitedClis(dir: string, own: string): void { | ||
| let names: string[] = []; | ||
| try { | ||
| names = readdirSync(dir); | ||
| } catch { | ||
| return; | ||
| } | ||
| for (const name of names) { | ||
| if (name === own) continue; | ||
| const file = join(dir, name); | ||
| try { | ||
| const pid = Number(name); | ||
| const beating = Date.now() - statSync(file).mtimeMs <= RUNNING_STALE_MS; | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Non-blocking. The installer measures staleness on its own monotonic clock on purpose (its comment says the clock "stops while the machine sleeps"), while this compares wall-clock |
||
| // A live pid whose file stopped beating is a reused pid, not a CLI. | ||
| if (Number.isInteger(pid) && pid > 0 && isAlive(pid) && beating) continue; | ||
| unlinkSync(file); | ||
| } catch { | ||
| /* another CLI removed it first */ | ||
| } | ||
| } | ||
| } | ||
|
|
||
| /** Mark this process as running until it exits. Never throws: a failed write only lets an update land sooner. */ | ||
| export function registerRunningCli( | ||
| dir: string = RUNNING_DIR, | ||
|
|
@@ -16,6 +48,7 @@ export function registerRunningCli( | |
| mkdirSync(dir, { recursive: true, mode: 0o700 }); | ||
| const file = join(dir, String(process.pid)); | ||
| writeFileSync(file, "", { mode: 0o600 }); | ||
| pruneExitedClis(dir, String(process.pid)); | ||
| const heartbeat = setInterval(() => { | ||
| try { | ||
| mkdirSync(dir, { recursive: true, mode: 0o700 }); | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.