@parcha/pi-durable-disk 0.1.0-beta.12 → 0.1.0-beta.14
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/CHANGELOG.md +241 -0
- package/README.md +268 -28
- package/dist/app.d.ts +5 -0
- package/dist/app.js +3 -2
- package/dist/claim.d.ts +34 -2
- package/dist/claim.js +86 -5
- package/dist/cli.d.ts +6 -0
- package/dist/cli.js +98 -33
- package/dist/commands.d.ts +70 -0
- package/dist/commands.js +257 -0
- package/dist/confine.d.ts +47 -7
- package/dist/confine.js +148 -19
- package/dist/env.d.ts +17 -26
- package/dist/env.js +73 -68
- package/dist/errors.d.ts +2 -1
- package/dist/errors.js +1 -1
- package/dist/fork.d.ts +23 -0
- package/dist/fork.js +103 -3
- package/dist/hosts/daytona-launch.d.ts +3 -1
- package/dist/hosts/daytona-launch.js +30 -6
- package/dist/hosts/daytona.js +15 -3
- package/dist/hosts/local-host.d.ts +7 -1
- package/dist/hosts/local-host.js +30 -16
- package/dist/index.d.ts +31 -15
- package/dist/index.js +23 -15
- package/dist/internals.d.ts +3 -0
- package/dist/internals.js +6 -0
- package/dist/lease.d.ts +5 -2
- package/dist/lease.js +3 -1
- package/dist/local-run.d.ts +10 -0
- package/dist/local-run.js +64 -0
- package/dist/park.d.ts +4 -3
- package/dist/park.js +4 -3
- package/dist/process-table.d.ts +38 -0
- package/dist/process-table.js +158 -0
- package/dist/run.d.ts +52 -10
- package/dist/run.js +42 -7
- package/dist/status.d.ts +11 -3
- package/dist/status.js +15 -13
- package/dist/store.d.ts +14 -2
- package/dist/store.js +16 -4
- package/dist/supervise.d.ts +22 -3
- package/dist/supervise.js +40 -13
- package/dist/watchdog.d.ts +3 -1
- package/dist/watchdog.js +46 -13
- package/package.json +12 -7
package/dist/watchdog.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
// The lease watchdog on a thread of its own. A store commit is synchronous, so a FUSE daemon that stops
|
|
2
2
|
// answering blocks the instance's main thread inside the kernel, and with it every timer the main thread owns. This
|
|
3
|
-
// thread never touches the mount: it reads a clock reading the main thread shares, /proc and the cgroup,
|
|
4
|
-
// signals. Once the lease has lapsed it kills the instance's commands on every look, until the main thread runs again,
|
|
3
|
+
// thread never touches the mount: it reads a clock reading the main thread shares, /proc and the cgroup (on macOS, one
|
|
4
|
+
// ps listing), and sends signals. Once the lease has lapsed it kills the instance's commands on every look, until the main thread runs again,
|
|
5
5
|
// sees the flag and takes the normal fence path (exit 75).
|
|
6
|
+
import * as child_process from "node:child_process";
|
|
6
7
|
import * as fs from "node:fs";
|
|
7
8
|
import * as path from "node:path";
|
|
8
9
|
import { Worker } from "node:worker_threads";
|
|
@@ -49,20 +50,49 @@ function cgroupPids(instance, proc, sys) {
|
|
|
49
50
|
}
|
|
50
51
|
}
|
|
51
52
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
const kill = options.kill ?? ((pid, signal) => process.kill(pid, signal));
|
|
53
|
+
/** The instance's group and its direct children, from /proc. */
|
|
54
|
+
function procTree(instance, proc) {
|
|
55
55
|
const own = stat(proc, instance);
|
|
56
|
-
if (own === undefined) return
|
|
57
|
-
const
|
|
56
|
+
if (own === undefined) return undefined;
|
|
57
|
+
const children = [];
|
|
58
58
|
for (const entry of fs.readdirSync(proc)) {
|
|
59
59
|
const pid = Number(entry);
|
|
60
60
|
if (!Number.isInteger(pid) || pid <= 0 || pid === instance) continue;
|
|
61
61
|
const child = stat(proc, pid);
|
|
62
|
-
if (child
|
|
63
|
-
|
|
62
|
+
if (child !== undefined && child.ppid === instance) children.push({ pid, pgrp: child.pgrp });
|
|
63
|
+
}
|
|
64
|
+
return { pgrp: own.pgrp, children };
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/** The same from one ps listing, where there is no /proc (macOS). */
|
|
68
|
+
function psTree(instance, ps) {
|
|
69
|
+
let text;
|
|
70
|
+
try {
|
|
71
|
+
text = ps("/bin/ps", ["-A", "-o", "pid=,ppid=,pgid="]);
|
|
72
|
+
} catch {
|
|
73
|
+
return undefined;
|
|
74
|
+
}
|
|
75
|
+
const rows = [];
|
|
76
|
+
for (const line of text.split("\n")) {
|
|
77
|
+
const f = line.trim().split(/\s+/).map(Number);
|
|
78
|
+
if (f.length === 3 && f.every(Number.isInteger)) rows.push({ pid: f[0], ppid: f[1], pgrp: f[2] });
|
|
64
79
|
}
|
|
65
|
-
|
|
80
|
+
const own = rows.find((row) => row.pid === instance);
|
|
81
|
+
if (own === undefined) return undefined;
|
|
82
|
+
return { pgrp: own.pgrp, children: rows.filter((row) => row.ppid === instance && row.pid > 0 && row.pid !== instance) };
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const runPs = (file, args) => child_process.execFileSync(file, args, { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"], timeout: 10000 });
|
|
86
|
+
|
|
87
|
+
function sweep(instance, options = {}) {
|
|
88
|
+
const proc = options.proc ?? "/proc";
|
|
89
|
+
const kill = options.kill ?? ((pid, signal) => process.kill(pid, signal));
|
|
90
|
+
const ps = options.ps ?? (options.proc === undefined && process.platform === "darwin" ? runPs : undefined);
|
|
91
|
+
const tree = ps === undefined ? procTree(instance, proc) : psTree(instance, ps);
|
|
92
|
+
if (tree === undefined) return 0;
|
|
93
|
+
const targets = new Set();
|
|
94
|
+
for (const child of tree.children) targets.add(child.pgrp === tree.pgrp || child.pgrp <= 1 ? child.pid : -child.pgrp);
|
|
95
|
+
if (options.ownCgroup && ps === undefined) {
|
|
66
96
|
for (const pid of cgroupPids(instance, proc, options.sys ?? "/sys/fs/cgroup")) if (pid !== instance && pid > 0) targets.add(pid);
|
|
67
97
|
}
|
|
68
98
|
let sent = 0;
|
|
@@ -78,6 +108,7 @@ function sweep(instance, options = {}) {
|
|
|
78
108
|
}
|
|
79
109
|
`;
|
|
80
110
|
const WORKER_SOURCE = `"use strict";
|
|
111
|
+
const child_process = require("node:child_process");
|
|
81
112
|
const fs = require("node:fs");
|
|
82
113
|
const path = require("node:path");
|
|
83
114
|
const { workerData } = require("node:worker_threads");
|
|
@@ -88,14 +119,14 @@ setInterval(() => {
|
|
|
88
119
|
Atomics.add(shared, SLOT_KILLED, BigInt(sweep(workerData.instance, { ownCgroup: workerData.ownCgroup })));
|
|
89
120
|
}, workerData.checkMs);
|
|
90
121
|
`;
|
|
91
|
-
const compiled = new Function("fs", "path", `"use strict";${LIB}\nreturn { lapsed, sweep };`)(fs, path);
|
|
122
|
+
const compiled = new Function("child_process", "fs", "path", `"use strict";${LIB}\nreturn { lapsed, sweep };`)(child_process, fs, path);
|
|
92
123
|
/** One look at the shared state at `now`. True once the lease has lapsed, for good; the first look records the age. */
|
|
93
124
|
export const lapsed = compiled.lapsed;
|
|
94
125
|
/**
|
|
95
126
|
* SIGKILL the instance's commands: the process group of each direct child (pi starts each command as its own group's
|
|
96
127
|
* leader), or the child alone when it shares the instance's group; with `ownCgroup`, every other process of the cgroup.
|
|
97
128
|
* Never the instance or its group. A process this user may not signal (sudo's archil helpers) is skipped. Returns the
|
|
98
|
-
* number of signals sent. `stat` lines are `pid (comm) state ppid pgrp ...`, read after the last `)` since comm may hold
|
|
129
|
+
* number of signals sent. Where there is no /proc (macOS) the processes come from one ps listing, and there is no cgroup. `stat` lines are `pid (comm) state ppid pgrp ...`, read after the last `)` since comm may hold
|
|
99
130
|
* spaces and parentheses.
|
|
100
131
|
*/
|
|
101
132
|
export const sweep = compiled.sweep;
|
|
@@ -110,7 +141,9 @@ export class LeaseWatchdog {
|
|
|
110
141
|
Atomics.store(this.#shared, SLOT_BEAT, options.beatNs);
|
|
111
142
|
Atomics.store(this.#shared, SLOT_LIMIT, BigInt(Math.round(options.limitMs * 1e6)));
|
|
112
143
|
const data = { buffer, checkMs: options.checkMs, instance: process.pid, ownCgroup: options.ownCgroup === true };
|
|
113
|
-
|
|
144
|
+
// No inherited Node flags: the source is self-contained CommonJS and needs none of the host's, and some would make Node
|
|
145
|
+
// read it as ESM (`--input-type=module`, a host running inline ESM through -e or stdin), where `require` is undefined.
|
|
146
|
+
this.#worker = new Worker(WORKER_SOURCE, { eval: true, workerData: data, execArgv: [] });
|
|
114
147
|
this.#worker.unref();
|
|
115
148
|
}
|
|
116
149
|
/** A heartbeat write that started at `startNs` succeeded. */
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@parcha/pi-durable-disk",
|
|
3
|
-
"version": "0.1.0-beta.
|
|
3
|
+
"version": "0.1.0-beta.14",
|
|
4
4
|
"description": "A durable pi host on a network disk (Archil): one owner per run, a fence, durable commits, and a supervisor.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"pi",
|
|
@@ -30,6 +30,10 @@
|
|
|
30
30
|
"types": "./dist/lease.d.ts",
|
|
31
31
|
"default": "./dist/lease.js"
|
|
32
32
|
},
|
|
33
|
+
"./internals": {
|
|
34
|
+
"types": "./dist/internals.d.ts",
|
|
35
|
+
"default": "./dist/internals.js"
|
|
36
|
+
},
|
|
33
37
|
"./bin/archil-scoped": "./bin/archil-scoped",
|
|
34
38
|
"./package.json": "./package.json"
|
|
35
39
|
},
|
|
@@ -49,7 +53,7 @@
|
|
|
49
53
|
"build": "rm -rf dist && tsc -p tsconfig.build.json && node scripts/rewrite-dts.mjs dist",
|
|
50
54
|
"prepack": "npm run build",
|
|
51
55
|
"typecheck": "tsc --noEmit && tsc --noEmit -p examples",
|
|
52
|
-
"test": "node --test 'test/*.test.ts'",
|
|
56
|
+
"test": "node --test --test-timeout=240000 'test/*.test.ts' 'test/examples/*.test.ts'",
|
|
53
57
|
"test:acceptance": "node --test --test-concurrency=1 'test/acceptance/*.test.ts'",
|
|
54
58
|
"test:live": "node --test --test-concurrency=1 'test/live/*.test.ts'",
|
|
55
59
|
"test:live:claim": "node --test --test-concurrency=1 test/live/claim.live.test.ts",
|
|
@@ -58,20 +62,21 @@
|
|
|
58
62
|
"test:live:run": "node --test --test-concurrency=1 test/live/run.live.test.ts",
|
|
59
63
|
"test:live:store": "node --test --test-concurrency=1 test/live/store.live.test.ts",
|
|
60
64
|
"test:live:supervise": "node --test --test-concurrency=1 test/live/supervise.live.test.ts",
|
|
65
|
+
"test:live:supervise-slow": "node --test --test-concurrency=1 test/live/supervise-slow.live.test.ts",
|
|
61
66
|
"test:live:docker": "PDA_LIVE_DOCKER=1 node --test --test-concurrency=1 test/live/docker.live.test.ts test/live/docker-park.live.test.ts",
|
|
62
67
|
"verify:tarball": "node scripts/verify-tarball.mjs"
|
|
63
68
|
},
|
|
64
69
|
"peerDependencies": {
|
|
65
|
-
"@earendil-works/chord": "
|
|
66
|
-
"@earendil-works/pi-durable": "
|
|
70
|
+
"@earendil-works/chord": ">=1.0.4 <1.4.0",
|
|
71
|
+
"@earendil-works/pi-durable": ">=1.0.4 <1.4.0"
|
|
67
72
|
},
|
|
68
73
|
"dependencies": {
|
|
69
74
|
"disk": "1.7.0"
|
|
70
75
|
},
|
|
71
76
|
"devDependencies": {
|
|
72
|
-
"@earendil-works/chord": "1.0
|
|
73
|
-
"@earendil-works/pi-ai": "1.0
|
|
74
|
-
"@earendil-works/pi-durable": "1.0
|
|
77
|
+
"@earendil-works/chord": "1.1.0",
|
|
78
|
+
"@earendil-works/pi-ai": "1.1.0",
|
|
79
|
+
"@earendil-works/pi-durable": "1.1.0"
|
|
75
80
|
},
|
|
76
81
|
"repository": {
|
|
77
82
|
"type": "git",
|