@mark1russell7/harness 0.11.0 → 0.11.1
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 +6 -3
- package/build/index.d.ts +13 -0
- package/build/index.js +12 -6
- package/build/index.js.map +1 -1
- package/image/procedures/Dockerfile +3 -2
- package/image/procedures/client-ws.mjs +37 -12
- package/image/procedures/npm-lock.json +13 -16
- package/image/procedures/package.json +1 -1
- package/image/procedures/server-ws.mjs +5 -3
- package/package.json +1 -1
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
# The procedures image of the harness: @procedures/client 0.
|
|
1
|
+
# The procedures image of the harness: @procedures/client 0.7.0 and ws 8.22.0 on Node 24.21.0. Its WebSocket
|
|
2
2
|
# client runs as a container in the client's network namespace, as the harness's own TCP client
|
|
3
3
|
# does, and prints the same JSON lines (client-ws.mjs). A target runs the same image with the
|
|
4
4
|
# script of the procedures server (server-ws.mjs), thus the client and the server share one
|
|
5
|
-
# release of the package. The digest of the image and an npm lockfile pin them.
|
|
5
|
+
# release of the package. The digest of the image and an npm lockfile pin them. The lockfile
|
|
6
|
+
# holds one copy of ws, thus @procedures/client/node and the scripts use one ws class.
|
|
6
7
|
# lab.ts stages the build context: this folder, under harness/image/procedures. The lockfile is
|
|
7
8
|
# npm-lock.json here, because npm leaves each package-lock.json out of a published tarball.
|
|
8
9
|
FROM node:24.21.0-alpine@sha256:ebfe2f90462722a7a4de65e91990e97fe0d401c70e0e762c5b53302f905ec1c1
|
|
@@ -4,13 +4,18 @@
|
|
|
4
4
|
// --reconnect-attempts N --reconnect-jitter X]
|
|
5
5
|
// --request-timeout-ms N --connection-timeout-ms N [--hold-ms N] [--go PATH] [--max-s N]
|
|
6
6
|
//
|
|
7
|
-
// A cell's client: the WebSocket client of @procedures/client 0.
|
|
8
|
-
// 24.21.0.
|
|
9
|
-
//
|
|
10
|
-
//
|
|
11
|
-
//
|
|
12
|
-
//
|
|
13
|
-
//
|
|
7
|
+
// A cell's client: the WebSocket client of @procedures/client 0.7.0 (WebSocketTransport) on Node
|
|
8
|
+
// 24.21.0. --websocket gives the class of its sockets:
|
|
9
|
+
// global the WebSocketTransport of the root entry, whose default class is the global WebSocket
|
|
10
|
+
// of Node: undici 7.29.1.
|
|
11
|
+
// ws the WebSocketTransport of the Node entry @procedures/client/node, whose default class
|
|
12
|
+
// is the WebSocket of the ws package (since 0.4.0). The client gives the transport a
|
|
13
|
+
// subclass of that class, from the one copy of ws in the image: it stops when the Node
|
|
14
|
+
// entry resolves another copy.
|
|
15
|
+
// Each setting of the transport is a flag, and the client has no default: a missing flag is a
|
|
16
|
+
// failure. 0.7.0 takes each time as a duration, not a number. Both modes call `harness.echo`
|
|
17
|
+
// first, and its reply is `ready`. idle: then hold the connection. inflight: then wait for the file
|
|
18
|
+
// --go, and call `harness.echo` (or `harness.hold` for --hold-ms) into the fault.
|
|
14
19
|
//
|
|
15
20
|
// The lines are those of the harness's TCP client, on CLOCK_MONOTONIC, and the program's own acts:
|
|
16
21
|
// dial {conn} before each new socket of the transport (conn counts from 1)
|
|
@@ -27,9 +32,13 @@
|
|
|
27
32
|
// The client gives the transport a subclass of the class of its sockets. The subclass writes a
|
|
28
33
|
// line, then calls its class: the transport, undici and ws do what they do without it. Its
|
|
29
34
|
// listeners of `open` and `close` are its own, thus the transport cannot remove them. A drop line
|
|
30
|
-
// with no local address is a socket that never opened: a try that failed.
|
|
35
|
+
// with no local address is a socket that never opened: a try that failed. A WebSocket of ws emits
|
|
36
|
+
// no `error` for an error of its socket (lib/websocket.js:1411-1421), thus the subclass reads that
|
|
37
|
+
// error on the socket itself.
|
|
31
38
|
|
|
32
39
|
import { subscribe } from "node:diagnostics_channel";
|
|
40
|
+
import { createRequire } from "node:module";
|
|
41
|
+
import { fileURLToPath } from "node:url";
|
|
33
42
|
import { WebSocket as WsWebSocket } from "ws";
|
|
34
43
|
import { bootId, commandLine, emit, now, waitFor } from "./lines.mjs";
|
|
35
44
|
|
|
@@ -41,7 +50,7 @@ const mode = line.required("--mode");
|
|
|
41
50
|
if (mode !== "idle" && mode !== "inflight") line.fail("--mode is idle or inflight");
|
|
42
51
|
const socketClass = line.required("--websocket");
|
|
43
52
|
if (socketClass !== "global" && socketClass !== "ws") line.fail("--websocket is global or ws");
|
|
44
|
-
/** This function gives a duration of milliseconds, as 0.
|
|
53
|
+
/** This function gives a duration of milliseconds, as 0.7.0 takes each time. */
|
|
45
54
|
const ms = (value) => ({ value, unit: "ms" });
|
|
46
55
|
const heartbeat = line.toggle("--heartbeat")
|
|
47
56
|
? {
|
|
@@ -67,11 +76,22 @@ const hold = line.flag("--hold-ms") === undefined ? undefined : line.number("--h
|
|
|
67
76
|
const go = line.flag("--go") ?? "/tmp/go";
|
|
68
77
|
const max = Number(line.flag("--max-s") ?? 60) * 1000;
|
|
69
78
|
|
|
79
|
+
/** The entry of the transport for each class: the root for the global WebSocket, the Node entry for ws. */
|
|
80
|
+
const ENTRY = socketClass === "ws" ? "@procedures/client/node" : "@procedures/client";
|
|
81
|
+
// the class of the Node entry is the class of the ws that it resolves: it must be the ws of this client
|
|
82
|
+
if (socketClass === "ws") {
|
|
83
|
+
const nodeEntry = fileURLToPath(import.meta.resolve(ENTRY));
|
|
84
|
+
const theirs = createRequire(nodeEntry).resolve("ws");
|
|
85
|
+
const ours = createRequire(import.meta.url).resolve("ws");
|
|
86
|
+
if (theirs !== ours) line.fail(`${ENTRY} resolves ws at ${theirs}, and this client at ${ours}`);
|
|
87
|
+
}
|
|
88
|
+
|
|
70
89
|
emit("start", {
|
|
71
90
|
boot_id: bootId(),
|
|
72
91
|
mode,
|
|
73
92
|
client: "procedures-ws",
|
|
74
|
-
package: "@procedures/client 0.
|
|
93
|
+
package: "@procedures/client 0.7.0",
|
|
94
|
+
entry: ENTRY,
|
|
75
95
|
websocket: socketClass,
|
|
76
96
|
node: process.version,
|
|
77
97
|
undici: process.versions.undici ?? null,
|
|
@@ -120,10 +140,14 @@ const stamped = (Base) => {
|
|
|
120
140
|
super(...args);
|
|
121
141
|
const socket = { conn, local: undefined, dropped: false };
|
|
122
142
|
sockets.set(this, socket);
|
|
123
|
-
// a socket of ws gives its local address at its open
|
|
143
|
+
// a socket of ws gives its local address at its open, and undici gives it in a diagnostics
|
|
144
|
+
// channel. ws keeps the error of its socket to itself, thus the client reads it there
|
|
124
145
|
this.addEventListener("open", () => {
|
|
125
146
|
const raw = this._socket;
|
|
126
147
|
if (raw?.localAddress !== undefined) opened(socket, `${raw.localAddress}:${raw.localPort}`);
|
|
148
|
+
raw?.on?.("error", (error) => {
|
|
149
|
+
pending = { errno: error?.code ?? error?.name ?? "unknown", at: now() };
|
|
150
|
+
});
|
|
127
151
|
});
|
|
128
152
|
this.addEventListener("error", (event) => {
|
|
129
153
|
const code = event?.error?.code;
|
|
@@ -174,7 +198,8 @@ setTimeout(() => {
|
|
|
174
198
|
process.exit(0);
|
|
175
199
|
}, max);
|
|
176
200
|
|
|
177
|
-
const { Client
|
|
201
|
+
const { Client } = await import("@procedures/client");
|
|
202
|
+
const { WebSocketTransport } = await import(ENTRY);
|
|
178
203
|
|
|
179
204
|
let first;
|
|
180
205
|
const firstOpen = new Promise((resolve) => {
|
|
@@ -6,14 +6,14 @@
|
|
|
6
6
|
"": {
|
|
7
7
|
"name": "loom-harness-procedures",
|
|
8
8
|
"dependencies": {
|
|
9
|
-
"@procedures/client": "0.
|
|
9
|
+
"@procedures/client": "0.7.0",
|
|
10
10
|
"ws": "8.22.0"
|
|
11
11
|
}
|
|
12
12
|
},
|
|
13
13
|
"node_modules/@mark1russell7/algebra": {
|
|
14
|
-
"version": "0.
|
|
15
|
-
"resolved": "https://registry.npmjs.org/@mark1russell7/algebra/-/algebra-0.
|
|
16
|
-
"integrity": "sha512
|
|
14
|
+
"version": "0.7.1",
|
|
15
|
+
"resolved": "https://registry.npmjs.org/@mark1russell7/algebra/-/algebra-0.7.1.tgz",
|
|
16
|
+
"integrity": "sha512-+nimxDO9pEOwNxVJ7/F4vOAba8CnytN/DbRJKN7FxF/Q+g5Yy2u6e+3bbjiuq0eH9vCWwc3WlIuyDJhSFfuNow==",
|
|
17
17
|
"license": "MIT",
|
|
18
18
|
"dependencies": {
|
|
19
19
|
"@mark1russell7/quantity": "^0.2.0"
|
|
@@ -43,12 +43,12 @@
|
|
|
43
43
|
}
|
|
44
44
|
},
|
|
45
45
|
"node_modules/@mark1russell7/service": {
|
|
46
|
-
"version": "0.4.
|
|
47
|
-
"resolved": "https://registry.npmjs.org/@mark1russell7/service/-/service-0.4.
|
|
48
|
-
"integrity": "sha512-
|
|
46
|
+
"version": "0.4.1",
|
|
47
|
+
"resolved": "https://registry.npmjs.org/@mark1russell7/service/-/service-0.4.1.tgz",
|
|
48
|
+
"integrity": "sha512-9lxVPWJ/oZ1wfyl0NhZ7WX0w53L2HwG++78XmWr3gh0IhCiMZSJi3cuvgiRfF/uS2uf3u7TGXmb2MctEZDtF3g==",
|
|
49
49
|
"license": "MIT",
|
|
50
50
|
"dependencies": {
|
|
51
|
-
"@mark1russell7/algebra": "^0.
|
|
51
|
+
"@mark1russell7/algebra": "^0.7.0",
|
|
52
52
|
"@mark1russell7/quantity": "^0.2.0"
|
|
53
53
|
},
|
|
54
54
|
"engines": {
|
|
@@ -65,20 +65,17 @@
|
|
|
65
65
|
}
|
|
66
66
|
},
|
|
67
67
|
"node_modules/@procedures/client": {
|
|
68
|
-
"version": "0.
|
|
69
|
-
"resolved": "https://registry.npmjs.org/@procedures/client/-/client-0.
|
|
70
|
-
"integrity": "sha512-
|
|
68
|
+
"version": "0.7.0",
|
|
69
|
+
"resolved": "https://registry.npmjs.org/@procedures/client/-/client-0.7.0.tgz",
|
|
70
|
+
"integrity": "sha512-eBsvnfEYBJ7yrNpeX3Wi847Q9VlZMc8K92ydFfYIoZDwdfmj/Fxzu/IWQMfpCq7Fvof7Xt0m0Wk6YbcFZx8Frg==",
|
|
71
71
|
"license": "MIT",
|
|
72
72
|
"dependencies": {
|
|
73
|
-
"@mark1russell7/algebra": "^0.
|
|
73
|
+
"@mark1russell7/algebra": "^0.7.0",
|
|
74
74
|
"@mark1russell7/quantity": "^0.2.0",
|
|
75
|
-
"@mark1russell7/service": "^0.4.
|
|
75
|
+
"@mark1russell7/service": "^0.4.1",
|
|
76
76
|
"ws": "^8.18.3",
|
|
77
77
|
"zod": "^4.6.5"
|
|
78
78
|
},
|
|
79
|
-
"bin": {
|
|
80
|
-
"client-registry": "dist/procedures/cli/index.js"
|
|
81
|
-
},
|
|
82
79
|
"engines": {
|
|
83
80
|
"node": ">=22"
|
|
84
81
|
},
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// server-ws --port N --tcp-keepalive off|N --heartbeat off|on [--heartbeat-interval-ms N --heartbeat-misses N]
|
|
2
2
|
//
|
|
3
|
-
// A cell's target: the procedures server of @procedures/client 0.
|
|
3
|
+
// A cell's target: the procedures server of @procedures/client 0.7.0 (Server and
|
|
4
4
|
// WebSocketServerTransport) on the http.Server of Node 24.21.0, at the path /ws. It has two
|
|
5
5
|
// procedures: `harness.echo` answers at once, and `harness.hold` answers after `ms` milliseconds
|
|
6
6
|
// (a held reply, as BLPOP is for Redis).
|
|
@@ -10,7 +10,9 @@
|
|
|
10
10
|
//
|
|
11
11
|
// --heartbeat is the heartbeat of the server (roadmap R3): a WebSocket ping to each connection at
|
|
12
12
|
// each interval, and terminate() for a connection that missed `misses` pongs. Each setting is a
|
|
13
|
-
// flag, and the server has no default. 0.
|
|
13
|
+
// flag, and the server has no default. 0.7.0 takes each time as a duration, not a number. One
|
|
14
|
+
// setInterval of the transport, from its start, beats for all connections: the first ping of a
|
|
15
|
+
// connection comes at the next beat after its accept (transport.js:94-112, :147-151).
|
|
14
16
|
//
|
|
15
17
|
// Each event is a JSON line on the clock of the clients: a connection (`accepted`), a request,
|
|
16
18
|
// a reply, and the close of a connection. The `ws` package does the WebSocket of the server. Its
|
|
@@ -87,7 +89,7 @@ httpServer.listen(port, "0.0.0.0", () =>
|
|
|
87
89
|
port,
|
|
88
90
|
boot_id: bootId(),
|
|
89
91
|
node: process.version,
|
|
90
|
-
package: "@procedures/client 0.
|
|
92
|
+
package: "@procedures/client 0.7.0",
|
|
91
93
|
tcpKeepalive: keepAliveMs,
|
|
92
94
|
heartbeat,
|
|
93
95
|
}),
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://json.schemastore.org/package",
|
|
3
3
|
"name": "@mark1russell7/harness",
|
|
4
|
-
"version": "0.11.
|
|
4
|
+
"version": "0.11.1",
|
|
5
5
|
"description": "A fault-injection harness for connection timers: a cell runs a client against a stock target in Docker, through a topology of routers and proxies, a replicated group under Sentinels or a replica set, injects a fault, measures the kernel's own records and judges a prediction. HTTP clients of Go, Node and undici race the keep-alive of a Node server, and the MongoDB driver meets the faults of a replica set. The WebSocket client of @procedures/client drops a silent connection on its own heartbeat, and the harness times that detection and the reconnect from the program's lines and the kernel's stamps.",
|
|
6
6
|
"keywords": [
|
|
7
7
|
"fault-injection",
|