@gtkx/utils 2.0.0-beta.6 → 2.0.0-beta.8
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.
- package/README.md +43 -40
- package/dist/process/spawn-with-parent-death-signal.d.ts.map +1 -1
- package/dist/process/spawn-with-parent-death-signal.js +32 -4
- package/dist/process/spawn-with-parent-death-signal.js.map +1 -1
- package/package.json +4 -2
- package/src/process/spawn-with-parent-death-signal.ts +38 -4
package/README.md
CHANGED
|
@@ -5,9 +5,9 @@
|
|
|
5
5
|
<h1 align="center">GTKX</h1>
|
|
6
6
|
|
|
7
7
|
<p align="center">
|
|
8
|
-
The React framework for Linux
|
|
9
|
-
Write native
|
|
10
|
-
Real
|
|
8
|
+
The React framework for Linux.<br />
|
|
9
|
+
Write native GNOME applications with React and TypeScript.
|
|
10
|
+
Real Adwaita and GTK4 widgets with standard web tooling.
|
|
11
11
|
</p>
|
|
12
12
|
|
|
13
13
|
<p align="center">
|
|
@@ -38,11 +38,12 @@
|
|
|
38
38
|
|
|
39
39
|
## Demo
|
|
40
40
|
|
|
41
|
-
|
|
41
|
+
This app starts with an Adwaita application shell, renders native GTK4 widgets inside it, and uses ordinary React hooks and events:
|
|
42
42
|
|
|
43
43
|
```tsx
|
|
44
44
|
import * as Gtk from "@gtkx/gi/gtk";
|
|
45
|
-
import {
|
|
45
|
+
import { AdwApplication, AdwApplicationWindow, AdwHeaderBar, AdwToolbarView } from "@gtkx/jsx/adw";
|
|
46
|
+
import { GtkBox, GtkButton, GtkLabel } from "@gtkx/jsx/gtk";
|
|
46
47
|
import { createRoot, quit } from "@gtkx/react";
|
|
47
48
|
import { useState } from "react";
|
|
48
49
|
|
|
@@ -50,38 +51,40 @@ const Counter = () => {
|
|
|
50
51
|
const [count, setCount] = useState(0);
|
|
51
52
|
|
|
52
53
|
return (
|
|
53
|
-
<
|
|
54
|
+
<AdwApplicationWindow
|
|
54
55
|
title="Hello GTKX"
|
|
55
56
|
defaultWidth={400}
|
|
56
57
|
defaultHeight={300}
|
|
57
58
|
onCloseRequest={quit}
|
|
58
59
|
>
|
|
59
|
-
<
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
60
|
+
<AdwToolbarView topBar={<AdwHeaderBar />}>
|
|
61
|
+
<GtkBox
|
|
62
|
+
orientation={Gtk.Orientation.VERTICAL}
|
|
63
|
+
spacing={20}
|
|
64
|
+
marginTop={40}
|
|
65
|
+
marginBottom={40}
|
|
66
|
+
marginStart={40}
|
|
67
|
+
marginEnd={40}
|
|
68
|
+
valign={Gtk.Align.CENTER}
|
|
69
|
+
halign={Gtk.Align.CENTER}
|
|
70
|
+
>
|
|
71
|
+
<GtkLabel cssClasses={["title-1"]}>Welcome to GTKX!</GtkLabel>
|
|
72
|
+
<GtkLabel cssClasses={["title-2"]}>{`Count: ${String(count)}`}</GtkLabel>
|
|
73
|
+
<GtkButton
|
|
74
|
+
label="Increment"
|
|
75
|
+
onClicked={() => setCount((c) => c + 1)}
|
|
76
|
+
cssClasses={["suggested-action", "pill"]}
|
|
77
|
+
/>
|
|
78
|
+
</GtkBox>
|
|
79
|
+
</AdwToolbarView>
|
|
80
|
+
</AdwApplicationWindow>
|
|
78
81
|
);
|
|
79
82
|
};
|
|
80
83
|
|
|
81
84
|
const App = () => (
|
|
82
|
-
<
|
|
85
|
+
<AdwApplication>
|
|
83
86
|
<Counter />
|
|
84
|
-
</
|
|
87
|
+
</AdwApplication>
|
|
85
88
|
);
|
|
86
89
|
|
|
87
90
|
createRoot().render(<App />);
|
|
@@ -91,9 +94,9 @@ This is the [`hello-world`](https://github.com/gtkx-org/gtkx/tree/main/examples/
|
|
|
91
94
|
|
|
92
95
|
## Why GTKX
|
|
93
96
|
|
|
94
|
-
###
|
|
97
|
+
### Adwaita-first application development
|
|
95
98
|
|
|
96
|
-
|
|
99
|
+
GTKX uses Adwaita as the foundation for applications, windows, navigation, dialogs, and adaptive layouts, with the complete GTK4 toolkit underneath. It adds a declarative React layer and an integrated TypeScript toolchain to the GNOME platform:
|
|
97
100
|
|
|
98
101
|
- a React reconciler that exposes every GObject as a JSX element,
|
|
99
102
|
- a CLI for scaffolding, development, and production builds,
|
|
@@ -102,17 +105,17 @@ GTK4 is mature, and GtkBuilder XML can lay out a static interface, but nothing r
|
|
|
102
105
|
- a Testing Library-style API for querying and driving your widgets in tests,
|
|
103
106
|
- and a Model Context Protocol (MCP) server that exposes your live app to AI agents.
|
|
104
107
|
|
|
105
|
-
###
|
|
108
|
+
### Native GNOME widgets, without a compatibility layer
|
|
106
109
|
|
|
107
|
-
React Native and similar frameworks hide the native toolkit so one API can run everywhere. GTKX
|
|
110
|
+
React Native and similar frameworks hide the native toolkit so one API can run everywhere. GTKX renders real Adwaita and GTK4 widgets and exposes any other GObject-Introspection library on your system. It is GNOME-native and Linux-only by design.
|
|
108
111
|
|
|
109
112
|
### Why Node.js, and why generated bindings
|
|
110
113
|
|
|
111
114
|
GTKX runs on Node.js, which puts native modules, the npm ecosystem, and the tooling built for Node.js APIs within reach. GJS is GNOME's own runtime, built on SpiderMonkey rather than V8; node-gtk runs on Node.js but is lightly maintained, on the older nan/V8 ABI rather than N-API, and still centered on GTK3. The [Why GTKX guide](https://gtkx.dev/guide/why-gtkx) covers what running on Node.js means day to day.
|
|
112
115
|
|
|
113
|
-
GTKX generates the TypeScript types and
|
|
116
|
+
GTKX generates the TypeScript types and native FFI calls from the same GObject-Introspection data, so the types cannot drift from the calls they back. `Adw-1` is the sole default GIR root; its GIR include pulls in `Gtk-4.0`, so codegen covers both complete API surfaces. Neither belongs in a GTKX 2 `libraries` list.
|
|
114
117
|
|
|
115
|
-
At runtime, the native Rust core calls straight into the system
|
|
118
|
+
At runtime, the native Rust core calls straight into the system Adwaita, GTK4, and GLib libraries through libffi, without loading libgirepository at all.
|
|
116
119
|
|
|
117
120
|
## Quick start
|
|
118
121
|
|
|
@@ -121,10 +124,10 @@ GTKX is Linux-only and needs Node.js 26.7 or later. See [Requirements](#requirem
|
|
|
121
124
|
Scaffold a new app with the `create-gtkx` initializer:
|
|
122
125
|
|
|
123
126
|
```sh
|
|
124
|
-
npm create gtkx
|
|
127
|
+
npm create gtkx@beta
|
|
125
128
|
```
|
|
126
129
|
|
|
127
|
-
The same command works with other package managers: `pnpm create gtkx` or `yarn create gtkx`.
|
|
130
|
+
The same command works with other package managers: `pnpm create gtkx@beta` or `yarn create gtkx@beta`.
|
|
128
131
|
|
|
129
132
|
Then run your new app:
|
|
130
133
|
|
|
@@ -145,7 +148,7 @@ The documentation at **[gtkx.dev](https://gtkx.dev)** includes a step-by-step tu
|
|
|
145
148
|
|
|
146
149
|
GTKX is Linux-only. You need:
|
|
147
150
|
|
|
148
|
-
- Linux with the
|
|
151
|
+
- Linux with the Adwaita (1.8 or later), GTK4 (4.20 or later), and GLib development libraries
|
|
149
152
|
- Node.js 26.7 or later
|
|
150
153
|
|
|
151
154
|
The `@gtkx/native` addon ships prebuilt for x64 and arm64 glibc Linux; other targets need to build it from the GTKX repository, which requires a Rust toolchain.
|
|
@@ -154,10 +157,10 @@ The `@gtkx/native` addon ships prebuilt for x64 and arm64 glibc Linux; other tar
|
|
|
154
157
|
|
|
155
158
|
Explore the [example apps](https://github.com/gtkx-org/gtkx/tree/main/examples):
|
|
156
159
|
|
|
157
|
-
- [`hello-world`](https://github.com/gtkx-org/gtkx/tree/main/examples/hello-world): the counter above.
|
|
158
|
-
- [`gtk-demo`](https://github.com/gtkx-org/gtkx/tree/main/examples/gtk-demo):
|
|
159
|
-
- [`browser`](https://github.com/gtkx-org/gtkx/tree/main/examples/browser):
|
|
160
|
-
- [`animations`](https://github.com/gtkx-org/gtkx/tree/main/examples/animations):
|
|
160
|
+
- [`hello-world`](https://github.com/gtkx-org/gtkx/tree/main/examples/hello-world): the Adwaita counter above.
|
|
161
|
+
- [`gtk-demo`](https://github.com/gtkx-org/gtkx/tree/main/examples/gtk-demo): the official GTK4 widget showcase in an Adwaita application shell, covering lists, dialogs, gestures, CSS, and OpenGL.
|
|
162
|
+
- [`browser`](https://github.com/gtkx-org/gtkx/tree/main/examples/browser): an Adwaita browser built around `WebKitWebView`.
|
|
163
|
+
- [`animations`](https://github.com/gtkx-org/gtkx/tree/main/examples/animations): an Adwaita showcase for `@gtkx/animated`, with React Spring animations driven by the GTK frame clock.
|
|
161
164
|
- [`navigation`](https://github.com/gtkx-org/gtkx/tree/main/examples/navigation): a tour of `@gtkx/navigation`, React Navigation's stack, tab, and drawer navigators rendered with libadwaita.
|
|
162
165
|
- [`tutorial`](https://github.com/gtkx-org/gtkx/tree/main/examples/tutorial): the Tasks app the documentation builds.
|
|
163
166
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"spawn-with-parent-death-signal.d.ts","sourceRoot":"","sources":["../../src/process/spawn-with-parent-death-signal.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,YAAY,EAAS,KAAK,YAAY,EAAE,MAAM,oBAAoB,CAAC;
|
|
1
|
+
{"version":3,"file":"spawn-with-parent-death-signal.d.ts","sourceRoot":"","sources":["../../src/process/spawn-with-parent-death-signal.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,YAAY,EAAS,KAAK,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAkBjF,KAAK,uBAAuB,GAAG;IAC3B,KAAK,CAAC,EAAE,YAAY,CAAC;IACrB,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC;IACxB,kBAAkB,CAAC,EAAE,MAAM,EAAE,CAAC;CACjC,CAAC;AAEF,KAAK,4BAA4B,GAAG,IAAI,CAAC,uBAAuB,EAAE,oBAAoB,CAAC,GAAG;IACtF,gBAAgB,EAAE,MAAM,CAAC;CAC5B,CAAC;AAgRF,QAAA,MAAM,kBAAkB,QAAO,IAY9B,CAAC;AAiHF,iBAAS,0BAA0B,CAC/B,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,MAAM,EAAE,EACd,OAAO,GAAE,uBAA4B,GACtC,YAAY,CAOd;AAED,QAAA,MAAM,8BAA8B,GAChC,SAAS,MAAM,EACf,MAAM,MAAM,EAAE,EACd,SAAS,4BAA4B,KACtC,YA2CF,CAAC;AAEF,OAAO,EAAE,0BAA0B,EAAE,8BAA8B,EAAE,kBAAkB,EAAE,CAAC"}
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { spawn } from "node:child_process";
|
|
2
2
|
import { randomBytes } from "node:crypto";
|
|
3
|
-
import { readFileSync } from "node:fs";
|
|
3
|
+
import { readdirSync, readFileSync } from "node:fs";
|
|
4
4
|
import { Socket } from "node:net";
|
|
5
5
|
import { dirname, extname, join } from "node:path";
|
|
6
6
|
import { fileURLToPath } from "node:url";
|
|
7
|
+
import { warn } from "../log/default-logger.js";
|
|
7
8
|
import { killMarkedProcesses, PROCESS_MARKER } from "./kill-marked-processes.js";
|
|
8
9
|
import { cleanupDirectoryIdentity, killProcessGroup, processGroupIdentity, removeCleanupDirectory, } from "./kill-process-group.js";
|
|
9
10
|
import { resolveExecutable } from "./resolve-executable.js";
|
|
@@ -261,6 +262,34 @@ const captureCleanupDirectories = (command, paths) => {
|
|
|
261
262
|
}
|
|
262
263
|
return identities.filter((identity) => identity !== undefined);
|
|
263
264
|
};
|
|
265
|
+
const readResource = (path) => {
|
|
266
|
+
try {
|
|
267
|
+
return readFileSync(path, "utf8").trim();
|
|
268
|
+
}
|
|
269
|
+
catch {
|
|
270
|
+
return "unknown";
|
|
271
|
+
}
|
|
272
|
+
};
|
|
273
|
+
const openDescriptors = () => {
|
|
274
|
+
try {
|
|
275
|
+
return String(readdirSync("/proc/self/fd").length);
|
|
276
|
+
}
|
|
277
|
+
catch {
|
|
278
|
+
return "unknown";
|
|
279
|
+
}
|
|
280
|
+
};
|
|
281
|
+
const resourceSummary = () => `open descriptors ${openDescriptors()}, ` +
|
|
282
|
+
`cgroup pids ${readResource("/sys/fs/cgroup/pids.current")}/${readResource("/sys/fs/cgroup/pids.max")}`;
|
|
283
|
+
const spawnedProcessGroup = (child, command) => {
|
|
284
|
+
const processGroupId = child.pid;
|
|
285
|
+
if (processGroupId !== undefined) {
|
|
286
|
+
return processGroupIdentity(processGroupId);
|
|
287
|
+
}
|
|
288
|
+
child.on("error", (cause) => {
|
|
289
|
+
warn(`Cannot spawn ${command}: ${cause.code ?? cause.message}`);
|
|
290
|
+
});
|
|
291
|
+
return undefined;
|
|
292
|
+
};
|
|
264
293
|
const spawnGuarded = (command, options, signal, launchArguments) => {
|
|
265
294
|
const executable = resolveExecutable(command);
|
|
266
295
|
const setpriv = resolveExecutable("setpriv");
|
|
@@ -273,11 +302,10 @@ const spawnGuarded = (command, options, signal, launchArguments) => {
|
|
|
273
302
|
stdio: options.stdio ?? "ignore",
|
|
274
303
|
env: { ...(options.env ?? process.env), [PROCESS_MARKER]: jobValue },
|
|
275
304
|
});
|
|
276
|
-
const
|
|
277
|
-
const group = processGroupId === undefined ? undefined : processGroupIdentity(processGroupId);
|
|
305
|
+
const group = spawnedProcessGroup(child, command);
|
|
278
306
|
if (group === undefined) {
|
|
279
307
|
rollbackSpawn({ child, marker, cleanupDirectories });
|
|
280
|
-
throw new Error(`Failed to identify process group
|
|
308
|
+
throw new Error(`Failed to spawn ${command} or identify its process group (${resourceSummary()})`);
|
|
281
309
|
}
|
|
282
310
|
const job = { marker, processGroup: group, cleanupDirectories, signal };
|
|
283
311
|
registerJob(job);
|
|
@@ -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 { 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_KILL_GROUP_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];\nconst SUPERVISOR_REMOVE_RUNTIME_BODY = [\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_BODY = [...SUPERVISOR_KILL_GROUP_BODY, ...SUPERVISOR_REMOVE_RUNTIME_BODY];\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 \"matches_parent() {\",\n ' matches_process \"$expected_parent\" \"$expected_parent_start\"',\n \"}\",\n \"kill_group() {\",\n ...SUPERVISOR_KILL_GROUP_BODY.map((line) => ` ${line}`),\n \"}\",\n \"cleanup() {\",\n ' trap \"\" TERM INT HUP',\n \" kill_group\",\n ...SUPERVISOR_REMOVE_RUNTIME_BODY.map((line) => ` ${line}`),\n \" exit 0\",\n \"}\",\n \"trap cleanup TERM INT HUP\",\n 'while matches_process \"$group\" \"$group_start\" && matches_parent; do',\n ' \"$sleep_command\" 0.1',\n \"done\",\n \"kill_group\",\n \"while matches_parent; 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"]}
|
|
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 { readdirSync, readFileSync } from \"node:fs\";\nimport { Socket } from \"node:net\";\nimport { dirname, extname, join } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { warn } from \"../log/default-logger.ts\";\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_KILL_GROUP_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];\nconst SUPERVISOR_REMOVE_RUNTIME_BODY = [\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_BODY = [...SUPERVISOR_KILL_GROUP_BODY, ...SUPERVISOR_REMOVE_RUNTIME_BODY];\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 \"matches_parent() {\",\n ' matches_process \"$expected_parent\" \"$expected_parent_start\"',\n \"}\",\n \"kill_group() {\",\n ...SUPERVISOR_KILL_GROUP_BODY.map((line) => ` ${line}`),\n \"}\",\n \"cleanup() {\",\n ' trap \"\" TERM INT HUP',\n \" kill_group\",\n ...SUPERVISOR_REMOVE_RUNTIME_BODY.map((line) => ` ${line}`),\n \" exit 0\",\n \"}\",\n \"trap cleanup TERM INT HUP\",\n 'while matches_process \"$group\" \"$group_start\" && matches_parent; do',\n ' \"$sleep_command\" 0.1',\n \"done\",\n \"kill_group\",\n \"while matches_parent; 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 readResource = (path: string): string => {\n try {\n return readFileSync(path, \"utf8\").trim();\n } catch {\n return \"unknown\";\n }\n};\n\nconst openDescriptors = (): string => {\n try {\n return String(readdirSync(\"/proc/self/fd\").length);\n } catch {\n return \"unknown\";\n }\n};\n\nconst resourceSummary = (): string =>\n `open descriptors ${openDescriptors()}, ` +\n `cgroup pids ${readResource(\"/sys/fs/cgroup/pids.current\")}/${readResource(\"/sys/fs/cgroup/pids.max\")}`;\n\nconst spawnedProcessGroup = (child: ChildProcess, command: string): ProcessGroupIdentity | undefined => {\n const processGroupId = child.pid;\n\n if (processGroupId !== undefined) {\n return processGroupIdentity(processGroupId);\n }\n\n child.on(\"error\", (cause: NodeJS.ErrnoException) => {\n warn(`Cannot spawn ${command}: ${cause.code ?? cause.message}`);\n });\n\n return 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 group = spawnedProcessGroup(child, command);\n\n if (group === undefined) {\n rollbackSpawn({ child, marker, cleanupDirectories });\n throw new Error(`Failed to spawn ${command} or identify its process group (${resourceSummary()})`);\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,11 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gtkx/utils",
|
|
3
|
-
"version": "2.0.0-beta.
|
|
4
|
-
"description": "Shared utilities for the
|
|
3
|
+
"version": "2.0.0-beta.8",
|
|
4
|
+
"description": "Shared utilities for the GTKX GNOME app toolchain.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"gtkx",
|
|
7
7
|
"gtk",
|
|
8
8
|
"gtk4",
|
|
9
|
+
"adwaita",
|
|
10
|
+
"gnome",
|
|
9
11
|
"utils",
|
|
10
12
|
"linux",
|
|
11
13
|
"desktop"
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { type ChildProcess, spawn, type StdioOptions } from "node:child_process";
|
|
2
2
|
import { randomBytes } from "node:crypto";
|
|
3
|
-
import { readFileSync } from "node:fs";
|
|
3
|
+
import { readdirSync, readFileSync } from "node:fs";
|
|
4
4
|
import { Socket } from "node:net";
|
|
5
5
|
import { dirname, extname, join } from "node:path";
|
|
6
6
|
import { fileURLToPath } from "node:url";
|
|
7
|
+
import { warn } from "../log/default-logger.ts";
|
|
7
8
|
import { killMarkedProcesses, PROCESS_MARKER } from "./kill-marked-processes.ts";
|
|
8
9
|
import {
|
|
9
10
|
type CleanupDirectoryIdentity,
|
|
@@ -349,6 +350,40 @@ const captureCleanupDirectories = (command: string, paths: string[]): CleanupDir
|
|
|
349
350
|
return identities.filter((identity): identity is CleanupDirectoryIdentity => identity !== undefined);
|
|
350
351
|
};
|
|
351
352
|
|
|
353
|
+
const readResource = (path: string): string => {
|
|
354
|
+
try {
|
|
355
|
+
return readFileSync(path, "utf8").trim();
|
|
356
|
+
} catch {
|
|
357
|
+
return "unknown";
|
|
358
|
+
}
|
|
359
|
+
};
|
|
360
|
+
|
|
361
|
+
const openDescriptors = (): string => {
|
|
362
|
+
try {
|
|
363
|
+
return String(readdirSync("/proc/self/fd").length);
|
|
364
|
+
} catch {
|
|
365
|
+
return "unknown";
|
|
366
|
+
}
|
|
367
|
+
};
|
|
368
|
+
|
|
369
|
+
const resourceSummary = (): string =>
|
|
370
|
+
`open descriptors ${openDescriptors()}, ` +
|
|
371
|
+
`cgroup pids ${readResource("/sys/fs/cgroup/pids.current")}/${readResource("/sys/fs/cgroup/pids.max")}`;
|
|
372
|
+
|
|
373
|
+
const spawnedProcessGroup = (child: ChildProcess, command: string): ProcessGroupIdentity | undefined => {
|
|
374
|
+
const processGroupId = child.pid;
|
|
375
|
+
|
|
376
|
+
if (processGroupId !== undefined) {
|
|
377
|
+
return processGroupIdentity(processGroupId);
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
child.on("error", (cause: NodeJS.ErrnoException) => {
|
|
381
|
+
warn(`Cannot spawn ${command}: ${cause.code ?? cause.message}`);
|
|
382
|
+
});
|
|
383
|
+
|
|
384
|
+
return undefined;
|
|
385
|
+
};
|
|
386
|
+
|
|
352
387
|
const spawnGuarded = (
|
|
353
388
|
command: string,
|
|
354
389
|
options: ParentDeathSpawnOptions,
|
|
@@ -368,12 +403,11 @@ const spawnGuarded = (
|
|
|
368
403
|
env: { ...(options.env ?? process.env), [PROCESS_MARKER]: jobValue },
|
|
369
404
|
});
|
|
370
405
|
|
|
371
|
-
const
|
|
372
|
-
const group = processGroupId === undefined ? undefined : processGroupIdentity(processGroupId);
|
|
406
|
+
const group = spawnedProcessGroup(child, command);
|
|
373
407
|
|
|
374
408
|
if (group === undefined) {
|
|
375
409
|
rollbackSpawn({ child, marker, cleanupDirectories });
|
|
376
|
-
throw new Error(`Failed to identify process group
|
|
410
|
+
throw new Error(`Failed to spawn ${command} or identify its process group (${resourceSummary()})`);
|
|
377
411
|
}
|
|
378
412
|
|
|
379
413
|
const job: GuardJob = { marker, processGroup: group, cleanupDirectories, signal };
|