@gtkx/utils 2.0.0-beta.2 → 2.0.0-beta.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- {"version":3,"file":"spawn-with-parent-death-signal.js","sourceRoot":"","sources":["../../src/process/spawn-with-parent-death-signal.ts"],"names":[],"mappings":"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","sourcesContent":["import { type ChildProcess, spawn, type StdioOptions } from \"node:child_process\";\nimport { randomBytes } from \"node:crypto\";\nimport { Socket } from \"node:net\";\nimport { dirname, extname, join } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { killMarkedProcesses, PROCESS_MARKER } from \"./kill-marked-processes.ts\";\nimport {\n type CleanupDirectoryIdentity,\n cleanupDirectoryIdentity,\n killProcessGroup,\n type ProcessGroupIdentity,\n processGroupIdentity,\n removeCleanupDirectory,\n} from \"./kill-process-group.ts\";\nimport { resolveExecutable } from \"./resolve-executable.ts\";\n\ntype ParentDeathSpawnOptions = {\n stdio?: StdioOptions;\n env?: NodeJS.ProcessEnv;\n cleanupDirectories?: string[];\n};\n\ntype GuardJob = {\n marker: string;\n processGroup: ProcessGroupIdentity;\n cleanupDirectories: CleanupDirectoryIdentity[];\n};\n\ntype GuardState = {\n child?: ChildProcess | undefined;\n jobs: Map<string, GuardJob>;\n};\n\ntype SpawnRollback = {\n child: ChildProcess;\n marker: string;\n processGroup?: ProcessGroupIdentity | undefined;\n cleanupDirectories?: CleanupDirectoryIdentity[] | undefined;\n};\n\nconst MODULE_PATH = fileURLToPath(import.meta.url);\nconst GUARD_PATH = join(dirname(MODULE_PATH), `process-guard${extname(MODULE_PATH)}`);\nconst RUN_ID = randomBytes(8).toString(\"hex\");\nconst RUN_PREFIX = `${PROCESS_MARKER}=${RUN_ID}/`;\nconst guard: GuardState = { jobs: new Map() };\n\nconst writeGuardCommand = (child: ChildProcess, operation: \"+\" | \"-\", job: GuardJob): void => {\n child.stdin?.write(`${operation}${JSON.stringify(job)}\\n`);\n};\n\nconst releaseGuard = (child: ChildProcess): void => {\n child.unref();\n const { stdin } = child;\n\n if (stdin instanceof Socket) {\n stdin.unref();\n }\n};\n\nconst startGuard = (): void => {\n if (guard.child) {\n return;\n }\n\n const child = spawn(process.execPath, [GUARD_PATH, RUN_PREFIX], {\n detached: true,\n stdio: [\"pipe\", \"ignore\", \"ignore\"],\n });\n\n child.on(\"error\", () => {\n if (guard.child === child) {\n guard.child = undefined;\n }\n });\n\n child.on(\"exit\", () => {\n if (guard.child === child) {\n guard.child = undefined;\n }\n });\n\n child.stdin.on(\"error\", () => {\n if (guard.child === child) {\n guard.child = undefined;\n }\n });\n\n guard.child = child;\n\n for (const job of guard.jobs.values()) {\n writeGuardCommand(child, \"+\", job);\n }\n\n releaseGuard(child);\n};\n\nconst registerJob = (job: GuardJob): void => {\n guard.jobs.set(job.marker, job);\n\n if (guard.child) {\n writeGuardCommand(guard.child, \"+\", job);\n }\n};\n\nconst unregisterJob = (job: GuardJob): void => {\n guard.jobs.delete(job.marker);\n\n if (guard.child) {\n writeGuardCommand(guard.child, \"-\", job);\n }\n};\n\nconst rollbackSpawn = ({ child, marker, processGroup, cleanupDirectories = [] }: SpawnRollback): void => {\n if (processGroup === undefined) {\n child.kill(\"SIGKILL\");\n } else {\n killProcessGroup(processGroup);\n }\n\n killMarkedProcesses(marker);\n\n for (const identity of cleanupDirectories) {\n removeCleanupDirectory(identity);\n }\n};\n\nfunction spawnWithParentDeathSignal(\n command: string,\n args: string[],\n options: ParentDeathSpawnOptions = {},\n): ChildProcess {\n const executable = resolveExecutable(command);\n const setpriv = resolveExecutable(\"setpriv\");\n const jobValue = `${RUN_ID}/${randomBytes(4).toString(\"hex\")}`;\n const marker = `${PROCESS_MARKER}=${jobValue}`;\n startGuard();\n\n const child = spawn(setpriv, [\"--pdeathsig\", \"SIGKILL\", executable, ...args], {\n detached: true,\n stdio: options.stdio ?? \"ignore\",\n env: { ...(options.env ?? process.env), [PROCESS_MARKER]: jobValue },\n });\n\n const processGroupId = child.pid;\n const group = processGroupId === undefined ? undefined : processGroupIdentity(processGroupId);\n\n if (group === undefined) {\n rollbackSpawn({ child, marker });\n throw new Error(`Failed to identify process group for ${command}`);\n }\n\n const cleanupDirectories = (options.cleanupDirectories ?? []).map((path) => cleanupDirectoryIdentity(path));\n\n if (cleanupDirectories.includes(undefined)) {\n const captured = cleanupDirectories.filter(\n (identity): identity is CleanupDirectoryIdentity => identity !== undefined,\n );\n rollbackSpawn({ child, marker, processGroup: group, cleanupDirectories: captured });\n throw new Error(`Failed to identify a cleanup directory for ${command}`);\n }\n\n const job: GuardJob = {\n marker,\n processGroup: group,\n cleanupDirectories: cleanupDirectories.filter((identity) => identity !== undefined),\n };\n registerJob(job);\n\n child.on(\"exit\", () => {\n killMarkedProcesses(job.marker);\n unregisterJob(job);\n });\n\n return child;\n}\n\nexport { spawnWithParentDeathSignal };\n"]}
1
+ {"version":3,"file":"spawn-with-parent-death-signal.js","sourceRoot":"","sources":["../../src/process/spawn-with-parent-death-signal.ts"],"names":[],"mappings":"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","sourcesContent":["import { type ChildProcess, spawn, type StdioOptions } from \"node:child_process\";\nimport { randomBytes } from \"node:crypto\";\nimport { readFileSync } from \"node:fs\";\nimport { Socket } from \"node:net\";\nimport { dirname, extname, join } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { killMarkedProcesses, PROCESS_MARKER } from \"./kill-marked-processes.ts\";\nimport {\n type CleanupDirectoryIdentity,\n cleanupDirectoryIdentity,\n killProcessGroup,\n type ProcessGroupIdentity,\n processGroupIdentity,\n removeCleanupDirectory,\n} from \"./kill-process-group.ts\";\nimport { resolveExecutable } from \"./resolve-executable.ts\";\n\ntype ParentDeathSpawnOptions = {\n stdio?: StdioOptions;\n env?: NodeJS.ProcessEnv;\n cleanupDirectories?: string[];\n};\n\ntype ParentDeathSupervisorOptions = Omit<ParentDeathSpawnOptions, \"cleanupDirectories\"> & {\n cleanupDirectory: string;\n};\n\ntype GuardSignal = \"SIGKILL\" | \"SIGCONT\";\n\ntype ProcessIdentity = {\n pid: number;\n startTime: string;\n};\n\ntype ProcessWatch = {\n owner: ProcessIdentity;\n target: ProcessIdentity;\n};\n\ntype LaunchArguments = (executable: string, cleanupDirectories: CleanupDirectoryIdentity[]) => string[];\n\ntype GuardJob = {\n marker: string;\n processGroup: ProcessGroupIdentity;\n cleanupDirectories: CleanupDirectoryIdentity[];\n signal: GuardSignal;\n};\n\ntype GuardState = {\n child?: ChildProcess | undefined;\n jobs: Map<string, GuardJob>;\n watch?: ProcessWatch;\n};\n\ntype SpawnRollback = {\n child: ChildProcess;\n marker: string;\n processGroup?: ProcessGroupIdentity;\n cleanupDirectories?: CleanupDirectoryIdentity[];\n};\n\nconst MODULE_PATH = fileURLToPath(import.meta.url);\nconst GUARD_PATH = join(dirname(MODULE_PATH), `process-guard${extname(MODULE_PATH)}`);\nconst RUN_ID = randomBytes(8).toString(\"hex\");\nconst RUN_PREFIX = `${PROCESS_MARKER}=${RUN_ID}/`;\nconst guard: GuardState = { jobs: new Map() };\nconst SUPERVISOR_NAME = \"gtkx-process-supervisor\";\nconst SUPERVISOR_CLEANUP_NAME = \"gtkx-process-cleanup\";\nconst SUPERVISOR_WATCH_NAME = \"gtkx-process-watch\";\nconst SUPERVISOR_CLEANUP_BODY = [\n \"remaining=40\",\n 'while [ \"$remaining\" -gt 0 ] && kill -KILL \"-$group\" 2>/dev/null; do',\n ' \"$sleep_command\" 0.025',\n \" remaining=$((remaining - 1))\",\n \"done\",\n \"remaining=40\",\n 'while [ \"$remaining\" -gt 0 ] && ' +\n '[ \"$(\"$stat_command\" -c \"%d:%i:%u\" -- \"$runtime\" 2>/dev/null)\" = \"$identity\" ]; do',\n ' \"$rm_command\" -rf -- \"$runtime\"',\n ' \"$sleep_command\" 0.025',\n \" remaining=$((remaining - 1))\",\n \"done\",\n];\nconst SUPERVISOR_CLEANUP_SCRIPT = [\n \"runtime=$1\",\n \"identity=$2\",\n \"group=$3\",\n \"stat_command=$4\",\n \"rm_command=$5\",\n \"sleep_command=$6\",\n ...SUPERVISOR_CLEANUP_BODY,\n].join(\"\\n\");\nconst SUPERVISOR_WATCH_SCRIPT = [\n \"runtime=$1\",\n \"identity=$2\",\n \"group=$3\",\n \"group_start=$4\",\n \"expected_parent=$5\",\n \"expected_parent_start=$6\",\n \"stat_command=$7\",\n \"rm_command=$8\",\n \"sleep_command=$9\",\n \"matches_process() {\",\n \" expected_process=$1\",\n \" expected_start=$2\",\n \" process_stat=\",\n ' IFS= read -r process_stat < \"/proc/$expected_process/stat\" || return 1',\n \" process_tail=${process_stat##*) }\",\n \" set -- $process_tail\",\n ' case \"$1\" in Z|X|x) return 1 ;; esac',\n ' [ \"${20}\" = \"$expected_start\" ]',\n \"}\",\n \"cleanup() {\",\n ' trap \"\" TERM INT HUP',\n ...SUPERVISOR_CLEANUP_BODY.map((line) => ` ${line}`),\n \" exit 0\",\n \"}\",\n \"trap cleanup TERM INT HUP\",\n 'while matches_process \"$group\" \"$group_start\" && ' +\n 'matches_process \"$expected_parent\" \"$expected_parent_start\"; do',\n ' \"$sleep_command\" 0.1',\n \"done\",\n \"cleanup\",\n].join(\"\\n\");\nconst SUPERVISOR_SCRIPT = [\n \"runtime=$1\",\n \"identity=$2\",\n \"expected_parent=$3\",\n \"expected_parent_start=$4\",\n \"stat_command=$5\",\n \"rm_command=$6\",\n \"shell_command=$7\",\n \"setsid_command=$8\",\n \"sleep_command=$9\",\n \"shift 9\",\n \"watch_name=$1\",\n \"watch_script=$2\",\n \"cleanup_name=$3\",\n \"cleanup_script=$4\",\n \"shift 4\",\n \"process_start() {\",\n \" process_stat=\",\n ' IFS= read -r process_stat < \"/proc/$1/stat\" || return 1',\n \" process_tail=${process_stat##*) }\",\n \" set -- $process_tail\",\n ' case \"$1\" in Z|X|x) return 1 ;; esac',\n ' printf \"%s\" \"${20}\"',\n \"}\",\n \"matches_parent() {\",\n \" parent_stat=\",\n ' IFS= read -r parent_stat < \"/proc/$expected_parent/stat\" || return 1',\n \" parent_tail=${parent_stat##*) }\",\n \" set -- $parent_tail\",\n ' case \"$1\" in Z|X|x) return 1 ;; esac',\n ' [ \"${20}\" = \"$expected_parent_start\" ]',\n \"}\",\n \"terminate() {\",\n ' trap \"\" CONT TERM INT HUP',\n \" while :; do\",\n ' \"$setsid_command\" \"$shell_command\" -c \"$cleanup_script\" \"$cleanup_name\" \"$runtime\" \"$identity\" \"$$\" ' +\n '\"$stat_command\" \"$rm_command\" \"$sleep_command\" </dev/null >/dev/null 2>&1 &',\n \" cleanup=$!\",\n ' wait \"$cleanup\" 2>/dev/null',\n ' \"$sleep_command\" 0.025',\n \" done\",\n \"}\",\n \"continue_or_terminate() {\",\n \" remaining=40\",\n ' while matches_parent && [ \"$remaining\" -gt 0 ]; do',\n ' \"$sleep_command\" 0.025',\n \" remaining=$((remaining - 1))\",\n \" done\",\n \" if ! matches_parent; then terminate; fi\",\n \"}\",\n \"trap continue_or_terminate CONT\",\n \"trap terminate TERM INT HUP\",\n \"supervisor_start=$(process_start \\\"$$\\\") || terminate\",\n '( unset GTKX_PROCESS_GUARD; exec \"$setsid_command\" \"$shell_command\" -c \"$watch_script\" \"$watch_name\" \"$runtime\" ' +\n '\"$identity\" \"$$\" \"$supervisor_start\" \"$expected_parent\" \"$expected_parent_start\" \"$stat_command\" ' +\n '\"$rm_command\" \"$sleep_command\" ) </dev/null >/dev/null 2>&1 &',\n \"child=\",\n \"if ! matches_parent; then terminate; fi\",\n '\"$@\" &',\n \"child=$!\",\n \"while :; do\",\n ' wait \"$child\"',\n \" status=$?\",\n ' if kill -0 \"$child\" 2>/dev/null; then continue; fi',\n ' exit \"$status\"',\n \"done\",\n].join(\"\\n\");\n\nconst processIdentity = (pid: number): ProcessIdentity => {\n const stat = readFileSync(`/proc/${String(pid)}/stat`, \"utf8\");\n const fields = stat.slice(stat.lastIndexOf(\") \") + 2).split(\" \", 20);\n const state = fields[0];\n const startTime = fields[19];\n\n if (startTime === undefined || state === undefined || [\"Z\", \"X\", \"x\"].includes(state)) {\n throw new Error(`Failed to identify process ${String(pid)}`);\n }\n\n return { pid, startTime };\n};\n\nconst writeGuardCommand = (child: ChildProcess, operation: \"+\" | \"-\", job: GuardJob): void => {\n child.stdin?.write(`${operation}${JSON.stringify(job)}\\n`);\n};\n\nconst releaseGuard = (child: ChildProcess): void => {\n child.unref();\n const { stdin } = child;\n\n if (stdin instanceof Socket) {\n stdin.unref();\n }\n};\n\nconst isSameProcessWatch = (left: ProcessWatch, right: ProcessWatch): boolean =>\n left.owner.pid === right.owner.pid &&\n left.owner.startTime === right.owner.startTime &&\n left.target.pid === right.target.pid &&\n left.target.startTime === right.target.startTime;\n\nconst configureProcessWatch = (watch?: ProcessWatch): void => {\n if (watch === undefined) {\n return;\n }\n\n if (guard.watch !== undefined && !isSameProcessWatch(guard.watch, watch)) {\n throw new Error(\"The process guard is already watching another owner\");\n }\n\n if (guard.child !== undefined && guard.watch === undefined) {\n throw new Error(\"The process guard was started before its owner was registered\");\n }\n\n guard.watch = watch;\n};\n\nconst guardArguments = (): string[] =>\n guard.watch === undefined\n ? [GUARD_PATH, RUN_PREFIX]\n : [GUARD_PATH, RUN_PREFIX, JSON.stringify(guard.watch)];\n\nconst startGuard = (watch?: ProcessWatch): void => {\n configureProcessWatch(watch);\n\n if (guard.child) {\n return;\n }\n\n const child = spawn(process.execPath, guardArguments(), {\n detached: true,\n stdio: [\"pipe\", \"ignore\", \"ignore\"],\n });\n\n child.on(\"error\", () => {\n if (guard.child === child) {\n guard.child = undefined;\n }\n });\n\n child.on(\"exit\", () => {\n if (guard.child === child) {\n guard.child = undefined;\n }\n });\n\n child.stdin.on(\"error\", () => {\n if (guard.child === child) {\n guard.child = undefined;\n }\n });\n\n guard.child = child;\n\n for (const job of guard.jobs.values()) {\n writeGuardCommand(child, \"+\", job);\n }\n\n releaseGuard(child);\n};\n\nconst watchParentProcess = (): void => {\n const parentId = process.ppid;\n const watch = {\n owner: processIdentity(parentId),\n target: processIdentity(process.pid),\n };\n\n if (process.ppid !== parentId) {\n throw new Error(\"The parent process exited while its guard was starting\");\n }\n\n startGuard(watch);\n};\n\nconst registerJob = (job: GuardJob): void => {\n guard.jobs.set(job.marker, job);\n\n if (guard.child) {\n writeGuardCommand(guard.child, \"+\", job);\n }\n};\n\nconst unregisterJob = (job: GuardJob): void => {\n guard.jobs.delete(job.marker);\n\n if (guard.child) {\n writeGuardCommand(guard.child, \"-\", job);\n }\n};\n\nconst rollbackSpawn = ({ child, marker, processGroup, cleanupDirectories = [] }: SpawnRollback): void => {\n if (processGroup === undefined) {\n child.kill(\"SIGKILL\");\n } else {\n killProcessGroup(processGroup);\n }\n\n killMarkedProcesses(marker);\n\n for (const identity of cleanupDirectories) {\n removeCleanupDirectory(identity);\n }\n};\n\nconst captureCleanupDirectories = (command: string, paths: string[]): CleanupDirectoryIdentity[] => {\n const identities = paths.map((path) => cleanupDirectoryIdentity(path));\n\n if (identities.includes(undefined)) {\n throw new Error(`Failed to identify a cleanup directory for ${command}`);\n }\n\n return identities.filter((identity): identity is CleanupDirectoryIdentity => identity !== undefined);\n};\n\nconst spawnGuarded = (\n command: string,\n options: ParentDeathSpawnOptions,\n signal: GuardSignal,\n launchArguments: LaunchArguments,\n): ChildProcess => {\n const executable = resolveExecutable(command);\n const setpriv = resolveExecutable(\"setpriv\");\n const jobValue = `${RUN_ID}/${randomBytes(4).toString(\"hex\")}`;\n const marker = `${PROCESS_MARKER}=${jobValue}`;\n const cleanupDirectories = captureCleanupDirectories(command, options.cleanupDirectories ?? []);\n startGuard();\n\n const child = spawn(setpriv, launchArguments(executable, cleanupDirectories), {\n detached: true,\n stdio: options.stdio ?? \"ignore\",\n env: { ...(options.env ?? process.env), [PROCESS_MARKER]: jobValue },\n });\n\n const processGroupId = child.pid;\n const group = processGroupId === undefined ? undefined : processGroupIdentity(processGroupId);\n\n if (group === undefined) {\n rollbackSpawn({ child, marker, cleanupDirectories });\n throw new Error(`Failed to identify process group for ${command}`);\n }\n\n const job: GuardJob = { marker, processGroup: group, cleanupDirectories, signal };\n registerJob(job);\n\n child.on(\"exit\", () => {\n killMarkedProcesses(job.marker);\n unregisterJob(job);\n });\n\n return child;\n};\n\nfunction spawnWithParentDeathSignal(\n command: string,\n args: string[],\n options: ParentDeathSpawnOptions = {},\n): ChildProcess {\n return spawnGuarded(\n command,\n options,\n \"SIGKILL\",\n (executable) => [\"--pdeathsig\", \"SIGKILL\", executable, ...args],\n );\n}\n\nconst spawnWithParentDeathSupervisor = (\n command: string,\n args: string[],\n options: ParentDeathSupervisorOptions,\n): ChildProcess => {\n const shell = resolveExecutable(\"sh\");\n const stat = resolveExecutable(\"stat\");\n const rm = resolveExecutable(\"rm\");\n const setsid = resolveExecutable(\"setsid\");\n const sleep = resolveExecutable(\"sleep\");\n\n return spawnGuarded(\n command,\n { ...options, cleanupDirectories: [options.cleanupDirectory] },\n \"SIGCONT\",\n (executable, cleanupDirectories) => {\n const identity = cleanupDirectories[0];\n\n if (identity === undefined) {\n throw new Error(`Failed to identify a cleanup directory for ${command}`);\n }\n\n return [\n \"--pdeathsig\",\n \"SIGCONT\",\n shell,\n \"-c\",\n SUPERVISOR_SCRIPT,\n SUPERVISOR_NAME,\n identity.path,\n `${identity.device}:${identity.inode}:${identity.userId}`,\n String(process.pid),\n processIdentity(process.pid).startTime,\n stat,\n rm,\n shell,\n setsid,\n sleep,\n SUPERVISOR_WATCH_NAME,\n SUPERVISOR_WATCH_SCRIPT,\n SUPERVISOR_CLEANUP_NAME,\n SUPERVISOR_CLEANUP_SCRIPT,\n executable,\n ...args,\n ];\n },\n );\n};\n\nexport { spawnWithParentDeathSignal, spawnWithParentDeathSupervisor, watchParentProcess };\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gtkx/utils",
3
- "version": "2.0.0-beta.2",
3
+ "version": "2.0.0-beta.4",
4
4
  "description": "Shared utilities for the GTK toolchain: logging, errors, strings.",
5
5
  "keywords": [
6
6
  "gtkx",
@@ -1,9 +1,16 @@
1
1
  export { exitCodeForSignal } from "./exit-code-for-signal.ts";
2
2
  export { installGracefulShutdown } from "./install-graceful-shutdown.ts";
3
3
  export {
4
+ type CleanupDirectoryIdentity,
5
+ cleanupDirectoryIdentity,
4
6
  killProcessGroup,
5
7
  type ProcessGroupIdentity,
6
8
  processGroupIdentity,
9
+ removeCleanupDirectory,
7
10
  } from "./kill-process-group.ts";
8
11
  export { resolveExecutable, tryResolveExecutable } from "./resolve-executable.ts";
9
- export { spawnWithParentDeathSignal } from "./spawn-with-parent-death-signal.ts";
12
+ export {
13
+ spawnWithParentDeathSignal,
14
+ spawnWithParentDeathSupervisor,
15
+ watchParentProcess,
16
+ } from "./spawn-with-parent-death-signal.ts";
@@ -41,13 +41,13 @@ const isCurrentProcessGroup = (identity: ProcessGroupIdentity): boolean => {
41
41
  return current?.leaderStartTime === identity.leaderStartTime;
42
42
  };
43
43
 
44
- const killProcessGroup = (identity: ProcessGroupIdentity): void => {
44
+ const killProcessGroup = (identity: ProcessGroupIdentity, signal: NodeJS.Signals = "SIGKILL"): void => {
45
45
  if (!isCurrentProcessGroup(identity)) {
46
46
  return;
47
47
  }
48
48
 
49
49
  try {
50
- process.kill(-identity.processGroupId, "SIGKILL");
50
+ process.kill(-identity.processGroupId, signal);
51
51
  } catch {
52
52
  return;
53
53
  }
@@ -1,17 +1,34 @@
1
+ import { readFileSync } from "node:fs";
1
2
  import { killMarkedProcesses, killMarkedProcessRun } from "./kill-marked-processes.ts";
2
3
  import {
3
4
  type CleanupDirectoryIdentity,
4
5
  killProcessGroup,
5
6
  type ProcessGroupIdentity,
7
+ processGroupIdentity,
6
8
  removeCleanupDirectory,
7
9
  } from "./kill-process-group.ts";
8
10
 
9
11
  const GUARD_PREFIX = process.argv[2] ?? "";
12
+ const PROCESS_WATCH_ARGUMENT = process.argv[3];
10
13
  const WATCHED_SIGNALS = ["SIGTERM", "SIGINT", "SIGHUP"] as const satisfies NodeJS.Signals[];
14
+ const OWNER_POLL_INTERVAL_MS = 50;
15
+ const SUPERVISOR_EXIT_TIMEOUT_MS = 2000;
16
+
17
+ type ProcessIdentity = {
18
+ pid: number;
19
+ startTime: string;
20
+ };
21
+
22
+ type ProcessWatch = {
23
+ owner: ProcessIdentity;
24
+ target: ProcessIdentity;
25
+ };
26
+
11
27
  type GuardJob = {
12
28
  marker: string;
13
29
  processGroup: ProcessGroupIdentity;
14
30
  cleanupDirectories: CleanupDirectoryIdentity[];
31
+ signal: NodeJS.Signals;
15
32
  };
16
33
 
17
34
  const state: { bufferedCommands: string; isSweeping: boolean; jobs: Map<string, GuardJob> } = {
@@ -23,6 +40,17 @@ const state: { bufferedCommands: string; isSweeping: boolean; jobs: Map<string,
23
40
  const isRecord = (value: unknown): value is Record<string, unknown> =>
24
41
  typeof value === "object" && value !== null && !Array.isArray(value);
25
42
 
43
+ const isProcessIdentity = (value: unknown): value is ProcessIdentity =>
44
+ isRecord(value) &&
45
+ typeof value.pid === "number" &&
46
+ Number.isSafeInteger(value.pid) &&
47
+ value.pid > 1 &&
48
+ typeof value.startTime === "string" &&
49
+ /^\d+$/.test(value.startTime);
50
+
51
+ const isProcessWatch = (value: unknown): value is ProcessWatch =>
52
+ isRecord(value) && isProcessIdentity(value.owner) && isProcessIdentity(value.target);
53
+
26
54
  const isProcessGroupIdentity = (value: unknown): value is ProcessGroupIdentity =>
27
55
  isRecord(value) &&
28
56
  typeof value.processGroupId === "number" &&
@@ -43,7 +71,52 @@ const isGuardJob = (value: unknown): value is GuardJob =>
43
71
  typeof value.marker === "string" &&
44
72
  isProcessGroupIdentity(value.processGroup) &&
45
73
  Array.isArray(value.cleanupDirectories) &&
46
- value.cleanupDirectories.every(isCleanupDirectoryIdentity);
74
+ value.cleanupDirectories.every(isCleanupDirectoryIdentity) &&
75
+ (value.signal === "SIGKILL" || value.signal === "SIGCONT");
76
+
77
+ const parseProcessWatch = (): ProcessWatch | undefined => {
78
+ if (PROCESS_WATCH_ARGUMENT === undefined) {
79
+ return undefined;
80
+ }
81
+
82
+ try {
83
+ const value: unknown = JSON.parse(PROCESS_WATCH_ARGUMENT);
84
+
85
+ return isProcessWatch(value) ? value : undefined;
86
+ } catch {
87
+ return undefined;
88
+ }
89
+ };
90
+
91
+ const currentProcessIdentity = (pid: number): ProcessIdentity | undefined => {
92
+ try {
93
+ const stat = readFileSync(`/proc/${String(pid)}/stat`, "utf8");
94
+ const fields = stat.slice(stat.lastIndexOf(") ") + 2).split(" ", 20);
95
+ const state = fields[0];
96
+ const startTime = fields[19];
97
+
98
+ return startTime !== undefined && state !== undefined && !["Z", "X", "x"].includes(state)
99
+ ? { pid, startTime }
100
+ : undefined;
101
+ } catch {
102
+ return undefined;
103
+ }
104
+ };
105
+
106
+ const isCurrentProcess = (identity: ProcessIdentity): boolean =>
107
+ currentProcessIdentity(identity.pid)?.startTime === identity.startTime;
108
+
109
+ const killProcess = (identity: ProcessIdentity): void => {
110
+ if (!isCurrentProcess(identity)) {
111
+ return;
112
+ }
113
+
114
+ try {
115
+ process.kill(identity.pid, "SIGKILL");
116
+ } catch {
117
+ return;
118
+ }
119
+ };
47
120
 
48
121
  const applyCommand = (command: string): void => {
49
122
  const operation = command[0];
@@ -78,8 +151,11 @@ const receiveCommands = (chunk: Buffer | string): void => {
78
151
 
79
152
  const killJobs = (): void => {
80
153
  for (const job of state.jobs.values()) {
81
- killProcessGroup(job.processGroup);
82
- killMarkedProcesses(job.marker);
154
+ killProcessGroup(job.processGroup, job.signal);
155
+
156
+ if (job.signal === "SIGKILL") {
157
+ killMarkedProcesses(job.marker);
158
+ }
83
159
  }
84
160
  };
85
161
 
@@ -97,28 +173,103 @@ const removeCleanupDirectories = (): void => {
97
173
  }
98
174
  };
99
175
 
100
- const sweep = (): void => {
176
+ const hasRunningSupervisor = (): boolean => {
177
+ for (const job of state.jobs.values()) {
178
+ if (job.signal !== "SIGCONT") {
179
+ continue;
180
+ }
181
+
182
+ const current = processGroupIdentity(job.processGroup.processGroupId);
183
+
184
+ if (current?.leaderStartTime === job.processGroup.leaderStartTime) {
185
+ return true;
186
+ }
187
+ }
188
+
189
+ return false;
190
+ };
191
+
192
+ const forceSupervisors = (): void => {
193
+ for (const job of state.jobs.values()) {
194
+ if (job.signal === "SIGCONT") {
195
+ killProcessGroup(job.processGroup);
196
+ }
197
+ }
198
+ };
199
+
200
+ const finishSweep = (): void => {
201
+ if (GUARD_PREFIX.length > 0) {
202
+ killMarkedProcessRun(GUARD_PREFIX);
203
+ }
204
+
205
+ removeCleanupDirectories();
206
+ process.exit(0);
207
+ };
208
+
209
+ const awaitSupervisors = (deadline: number): void => {
210
+ if (hasRunningSupervisor() && Date.now() < deadline) {
211
+ setTimeout(() => {
212
+ awaitSupervisors(deadline);
213
+ }, OWNER_POLL_INTERVAL_MS);
214
+
215
+ return;
216
+ }
217
+
218
+ forceSupervisors();
219
+ finishSweep();
220
+ };
221
+
222
+ const sweep = (target?: ProcessIdentity): void => {
101
223
  if (state.isSweeping) {
102
224
  return;
103
225
  }
104
226
 
105
227
  state.isSweeping = true;
106
228
  applyCommand(state.bufferedCommands);
229
+
230
+ if (target !== undefined) {
231
+ killProcess(target);
232
+ }
233
+
107
234
  killJobs();
235
+ awaitSupervisors(Date.now() + SUPERVISOR_EXIT_TIMEOUT_MS);
236
+ };
108
237
 
109
- if (GUARD_PREFIX.length > 0) {
110
- killMarkedProcessRun(GUARD_PREFIX);
238
+ const watch = parseProcessWatch();
239
+
240
+ const startOwnerPoll = (): void => {
241
+ if (watch === undefined) {
242
+ return;
111
243
  }
112
244
 
113
- removeCleanupDirectories();
114
- process.exit(0);
245
+ const ownerPoll = setInterval(() => {
246
+ if (isCurrentProcess(watch.owner)) {
247
+ return;
248
+ }
249
+
250
+ clearInterval(ownerPoll);
251
+ sweep(watch.target);
252
+ }, OWNER_POLL_INTERVAL_MS);
253
+
254
+ if (!isCurrentProcess(watch.owner)) {
255
+ clearInterval(ownerPoll);
256
+ sweep(watch.target);
257
+ }
115
258
  };
116
259
 
260
+ startOwnerPoll();
261
+
117
262
  process.stdin.resume();
118
263
  process.stdin.on("data", receiveCommands);
119
- process.stdin.on("end", sweep);
120
- process.stdin.on("error", sweep);
264
+ process.stdin.on("end", () => {
265
+ sweep();
266
+ });
267
+ process.stdin.on("error", () => {
268
+ sweep();
269
+ });
121
270
 
122
271
  for (const signal of WATCHED_SIGNALS) {
123
- process.on(signal, sweep);
272
+ process.on(signal, () => {
273
+ sweep();
274
+ });
124
275
  }