@mark1russell7/harness 0.10.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 +9 -4
- package/build/index.d.ts +47 -2
- package/build/index.js +52 -12
- 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
package/README.md
CHANGED
|
@@ -107,10 +107,11 @@ A fault names a member by its role at the start of a run: `primary`, or `replica
|
|
|
107
107
|
- or by itself, a gap after its first reply (`gap`), for the keep-alive race.
|
|
108
108
|
- **The lines of an HTTP client.** It writes the lines of the TCP client, with the number `i` of the request on each line of a request. It also writes the connection of each try of a request (`request`, then `retry`), with its local address and a flag for a reused connection. Each connection gets a `conn` line, and each end of a connection a line (`eof`, `conn-error`, `closed`). The end of the second request is a `done` line.
|
|
109
109
|
- **A target with a script.** A target can take files before it starts (`Target.files`), for example the script of a Node server on the stock Node image. A target that writes JSON lines (`Target.lines`) gives them to each run (`Run.server`). The manifest holds the sha256 of each file and the first line of the target.
|
|
110
|
-
- **The WebSocket client** (`Cell.client` is `procedures-ws`) is the `WebSocketTransport` of `@procedures/client` 0.
|
|
110
|
+
- **The WebSocket client** (`Cell.client` is `procedures-ws`) is the `WebSocketTransport` of `@procedures/client` 0.7.0. The cell gives each of its settings (`probe.ws`): the class of its sockets, the heartbeat, the reconnect, and the times of a call.
|
|
111
111
|
- Its first request is `harness.echo`. In flight, its request into the fault is `harness.echo`, or `harness.hold` for a held reply.
|
|
112
112
|
- It writes the lines of the TCP client. It also writes a line for each act of the program: `ping`, `drop`, `reconnecting` and `dial`.
|
|
113
|
-
- The class of its sockets is the global `WebSocket` of Node,
|
|
113
|
+
- The class of its sockets is the global `WebSocket` of Node, with the transport of the root entry. Or it is the `WebSocket` of the `ws` package, with the transport of the Node entry `@procedures/client/node`.
|
|
114
|
+
- The transport gets a subclass of that class. The subclass writes a line, then gives the work to its class. Thus the transport, undici and ws operate as they do without it.
|
|
114
115
|
- The subclass has `terminate()` only when its class has it, because the transport drops a socket with `terminate()` when it exists.
|
|
115
116
|
- **A target from an image of the harness** (`Target.harness`). The target operates an image of the harness in place of its stock image. The procedures server is a script in the image of the WebSocket client (`server-ws.mjs`). Thus the client and the server use one release of the package. `Target.image` is the stock base of that image, and the manifest names both.
|
|
116
117
|
- **An agent** (`lab.agent(image, command, { pid })`) is a container in the network namespace of the target. With `pid`, it also shares the process namespace of the target, thus it can signal the server process. Its JSON lines and its `exec` stay available to the caller.
|
|
@@ -133,7 +134,7 @@ The harness builds six images. The tag of each image comes from the content of a
|
|
|
133
134
|
- The events of the driver are `sdam` lines (the heartbeats and the changes of each server), `cmap` lines (the pools) and `command` lines (each try of an operation).
|
|
134
135
|
- Each operation is a `sent` line, then an `ack` line or a `fail` line. An `ack` line has the server that accepted it. A `fail` line has the name and the labels of its error.
|
|
135
136
|
- The same image holds `stepdown-mongo.mjs`, the sender of `replSetStepDown`.
|
|
136
|
-
- **The procedures image** (`image/procedures`) holds `@procedures/client` 0.
|
|
137
|
+
- **The procedures image** (`image/procedures`) holds `@procedures/client` 0.7.0 and `ws` 8.22.0 on Node 24.21.0, whose WebSocket is undici 7.29.1. The digest of the image and an npm lockfile pin them. It holds the WebSocket client (`client-ws.mjs`) and the script of the procedures server (`server-ws.mjs`). Each takes each of its settings as a flag, and it has no default. The lockfile holds one copy of `ws`, and the client stops when `@procedures/client/node` resolves another one.
|
|
137
138
|
- **The router image** (`image/router`) is a digest-pinned Alpine with conntrack-tools, nftables, iproute2 and Python. `mbx.sh` sets the form of the router in one nftables table, `ctwatch.py` writes the conntrack events, and `resetter.py` sends the resets of the `reset-both` form. Each run records its packages in the manifest.
|
|
138
139
|
|
|
139
140
|
The build does the `cargo test` of the two crates. Thus the record decoder passes its tests before an image exists. Each amount that the native programs print is a quantity in a canonical unit. For example, an RTO is in `jiffy`, a round trip in `us`, and a segment in `B`.
|
|
@@ -202,7 +203,11 @@ Each run starts from kernels that keep nothing from the run before it (`lab.forg
|
|
|
202
203
|
- A drop counts when it comes before each report of the kernel. A report of the kernel that comes first is the detection, and the drop after it is not.
|
|
203
204
|
- The second view is the stamps of the program: from its `ping` line to its `drop` line. The steps are the later pings, each from the reference.
|
|
204
205
|
- The lines of a client name the socket by its local address (`local`), and the lines of a server by its peer (`peer`). For the server, the lines are the lines of the target. A line whose address is null is about a try that did not open.
|
|
206
|
+
- The harness reads the peer of a server from the records of its socket. Behind a router, that peer is the address of the router, not the address of the client.
|
|
207
|
+
- A first ping without an answer can come late, for example at a beat of a server whose phase no document gives. Then the prediction gives its latest time after the reply (`startsBy`), and the run waits that much longer.
|
|
205
208
|
- A prediction can say how the program drops (`drop`). A close frame (`frame`) is a write, and the socket stays ESTABLISHED after it. A close (`close`) closes the socket in δ_abs: a FIN or an abort. Another drop is a finding. A FIN of the peer that answers a close frame is no close by the program. The run gives the time from the `drop` line to the close by the program (`left`).
|
|
209
|
+
- The client process ends at the end of each run, and its kernel then closes each socket that is still open. A prediction can say that the program is that client process (`exits`). Then a close in δ_abs of that end is no close by the program: `left` stays null, and `exited` gives the time.
|
|
210
|
+
- The end of the process is the first change out of ESTABLISHED of another socket of the client after its last `dial` line (`exitOf`). The program dropped none of these sockets.
|
|
206
211
|
- A heartbeat is a timer of the program. The retransmit timer of its ping fires in the same interval. Thus a run whose retransmit timer was two jiffies or more behind its wheel slot is void: the VM stopped a vCPU.
|
|
207
212
|
- **The reconnect** (`reconnectOf`). A prediction with `reconnects` also reads what the client did after the detection.
|
|
208
213
|
- Each wait goes from a `reconnecting` line, where the program starts its timer, to its next `dial` line, where it makes the socket. A wait outside its band, widened by δ_abs, is a finding. A try that did not come is a finding too.
|
|
@@ -263,7 +268,7 @@ Each run starts from kernels that keep nothing from the run before it (`lab.forg
|
|
|
263
268
|
- **An HTTP client** (`Verdict.requests`). The verdict counts each outcome of the second request over the counted runs. A measurement-only cell also gives them in its reason.
|
|
264
269
|
- **The reconnect** (`Verdict.reconnect`). A wait outside its band is a finding of its run, thus the cell fails. The verdict gives the spread of each wait, of the first SYN, of the first ESTABLISHED and of the work before each connect(2). Its reason says the medians.
|
|
265
270
|
- **A call in flight** (`Verdict.calls`). The verdict counts each outcome of the call in flight of a WebSocket client, and its reason says them.
|
|
266
|
-
- **The socket after a drop** (`Verdict.left`). The verdict gives the spread of the time from a drop to the close of its socket by the program. It also counts the runs where the program did not close it.
|
|
271
|
+
- **The socket after a drop** (`Verdict.left`). The verdict gives the spread of the time from a drop to the close of its socket by the program. It also counts the runs where the program did not close it. For the runs where the socket stayed open until the end of the client process, it gives the spread of that end (`exited`).
|
|
267
272
|
|
|
268
273
|
The distribution test needs a simulator, which does not exist at this time.
|
|
269
274
|
|
package/build/index.d.ts
CHANGED
|
@@ -456,6 +456,12 @@ type Prediction = {
|
|
|
456
456
|
readonly low: Quantity;
|
|
457
457
|
/** This time is the closed form, plus the most that the timer can fire late, plus each proxy time. */
|
|
458
458
|
readonly high: Quantity;
|
|
459
|
+
/**
|
|
460
|
+
* This time is the latest reference of the band after the reply of the client. It is for a
|
|
461
|
+
* reference that can come late, for example the first ping without an answer of a server
|
|
462
|
+
* heartbeat. The run then waits that much longer for the detection.
|
|
463
|
+
*/
|
|
464
|
+
readonly startsBy?: Quantity;
|
|
459
465
|
readonly rtt?: RttRange;
|
|
460
466
|
readonly through?: readonly ProxyTime[];
|
|
461
467
|
readonly group?: GroupPrediction;
|
|
@@ -468,6 +474,12 @@ type Prediction = {
|
|
|
468
474
|
* ESTABLISHED at once (`close`).
|
|
469
475
|
*/
|
|
470
476
|
readonly drop?: "frame" | "close";
|
|
477
|
+
/**
|
|
478
|
+
* This flag tells that the program of the observer is the client process, which ends at the
|
|
479
|
+
* end of the run. Its kernel then closes each socket that is still open. A close of the
|
|
480
|
+
* socket at that end is not a close by the program, thus `left` stays null for it (`exited`).
|
|
481
|
+
*/
|
|
482
|
+
readonly exits?: true;
|
|
471
483
|
} | {
|
|
472
484
|
readonly detects: false;
|
|
473
485
|
readonly window: Quantity;
|
|
@@ -792,6 +804,13 @@ interface HopPlace {
|
|
|
792
804
|
type FaultPlace = "target" | "client" | HopPlace | GroupPlace;
|
|
793
805
|
/** This function tells if a fault place is a hop of a topology. */
|
|
794
806
|
export declare const isHopPlace: (place: FaultPlace | undefined) => place is HopPlace;
|
|
807
|
+
/**
|
|
808
|
+
* This function gives the latest end of a band in seconds, from the reply of the client. It is
|
|
809
|
+
* the high bound, after the latest reference of the band (`startsBy`).
|
|
810
|
+
*/
|
|
811
|
+
export declare const latestEndOf: (prediction: Extract<Prediction, {
|
|
812
|
+
readonly detects: true;
|
|
813
|
+
}>) => number;
|
|
795
814
|
/** A middlebox event is one line of the router: a change of a conntrack entry, or a reset that it sent. `t` is CLOCK_MONOTONIC in nanoseconds. */
|
|
796
815
|
interface MiddleboxEvent {
|
|
797
816
|
readonly ev: string;
|
|
@@ -923,12 +942,29 @@ export declare const programLines: (cell: Cell, run: Run) => readonly ProbeEvent
|
|
|
923
942
|
* the peer (CLOSE_WAIT) is not such a close. It is null when the socket did not close itself.
|
|
924
943
|
*/
|
|
925
944
|
export declare const leftAfter: (socket: readonly KernelRecord[], line: ProbeEvent) => Quantity | null;
|
|
945
|
+
/**
|
|
946
|
+
* This function gives the end of the client process in a run, as the kernel stamps it. The process
|
|
947
|
+
* ends after its last try, and its kernel then closes each socket that is still open, at one
|
|
948
|
+
* instant. Thus the end is the first change out of ESTABLISHED of another socket of the client
|
|
949
|
+
* after its last `dial` line. The program dropped none of these sockets. It is undefined when the
|
|
950
|
+
* record has no such change, for example when the last try did not open.
|
|
951
|
+
*/
|
|
952
|
+
export declare const exitOf: (run: Run, cookie: number | undefined) => number | undefined;
|
|
953
|
+
/**
|
|
954
|
+
* This function separates a close of the socket by the program from the end of the client process.
|
|
955
|
+
* `left` is the time from the drop line to the close, and `exit` is the end of the process. A close
|
|
956
|
+
* in δ_abs of that end is the end, not the program: `left` becomes null, and `exited` keeps the time.
|
|
957
|
+
*/
|
|
958
|
+
export declare const closeOrExit: (line: ProbeEvent, left: Quantity | null, exit: number | undefined, delta?: Quantity) => {
|
|
959
|
+
readonly left: Quantity | null;
|
|
960
|
+
readonly exited?: Quantity;
|
|
961
|
+
};
|
|
926
962
|
/**
|
|
927
963
|
* This function tells how a drop differs from the way that its prediction gives, or gives null.
|
|
928
964
|
* A close closes the socket in δ_abs. A close frame is a write, and the program does not close
|
|
929
965
|
* the socket at the drop. A peer that answers the close frame can close it, and that is no finding.
|
|
930
966
|
*/
|
|
931
|
-
export declare const dropFinding: (expected: "frame" | "close", drop: ProgramEvent, left: Quantity | null, delta?: Quantity) => string | null;
|
|
967
|
+
export declare const dropFinding: (expected: "frame" | "close", drop: ProgramEvent, left: Quantity | null, delta?: Quantity, exited?: Quantity) => string | null;
|
|
932
968
|
/** This function gives each ping of the program on the connection of the run, with the kernel's stamp of its write. */
|
|
933
969
|
export declare const pingsOf: (lines: readonly ProbeEvent[], socket: readonly KernelRecord[], local: unknown, delta?: Quantity) => ProgramEvent[];
|
|
934
970
|
/**
|
|
@@ -1239,9 +1275,15 @@ interface Measured {
|
|
|
1239
1275
|
/**
|
|
1240
1276
|
* This time is from the drop line of the program to the close of its socket by its own side: a
|
|
1241
1277
|
* FIN or an abort. A prediction that says how the program drops (`drop`) gets it. It is null
|
|
1242
|
-
* when the socket did not close itself in the run.
|
|
1278
|
+
* when the socket did not close itself in the run. A prediction can say that the client process
|
|
1279
|
+
* ends with the run (`exits`). Then a close at that end is no close by the program, and `left` is null.
|
|
1243
1280
|
*/
|
|
1244
1281
|
readonly left?: Quantity | null;
|
|
1282
|
+
/**
|
|
1283
|
+
* This time is from the drop line of the program to the end of the client process. A socket gets
|
|
1284
|
+
* it when it stayed open until that end. The kernel closed it then, not the program (`exits`).
|
|
1285
|
+
*/
|
|
1286
|
+
readonly exited?: Quantity;
|
|
1245
1287
|
/** This text is the reason that the run cannot count, or null. A void run is not a pass and not a finding. */
|
|
1246
1288
|
readonly void: string | null;
|
|
1247
1289
|
/** This text tells what in the run disagrees with the prediction, other than its timing, or null. */
|
|
@@ -1421,10 +1463,13 @@ interface Verdict {
|
|
|
1421
1463
|
/**
|
|
1422
1464
|
* This record is the time from a drop of the program to the close of its socket by its own side,
|
|
1423
1465
|
* over the counted runs. It also counts the runs where the socket did not close itself.
|
|
1466
|
+
* `exited` is the spread of the end of the client process, for the runs where the socket stayed
|
|
1467
|
+
* open until that end (`Measured.exited`).
|
|
1424
1468
|
*/
|
|
1425
1469
|
readonly left?: {
|
|
1426
1470
|
readonly spread: Spread | null;
|
|
1427
1471
|
readonly stayed: number;
|
|
1472
|
+
readonly exited?: Spread | null;
|
|
1428
1473
|
};
|
|
1429
1474
|
readonly why: string;
|
|
1430
1475
|
}
|
package/build/index.js
CHANGED
|
@@ -8,6 +8,11 @@ import { fileURLToPath } from "node:url";
|
|
|
8
8
|
//#region src/cell.ts
|
|
9
9
|
/** This function tells if a fault place is a hop of a topology. */
|
|
10
10
|
const isHopPlace = (place) => typeof place === "object" && "hop" in place;
|
|
11
|
+
/**
|
|
12
|
+
* This function gives the latest end of a band in seconds, from the reply of the client. It is
|
|
13
|
+
* the high bound, after the latest reference of the band (`startsBy`).
|
|
14
|
+
*/
|
|
15
|
+
const latestEndOf = (prediction) => valueIn(prediction.high, "s") + (prediction.startsBy === void 0 ? 0 : valueIn(prediction.startsBy, "s"));
|
|
11
16
|
//#endregion
|
|
12
17
|
//#region src/verdict.ts
|
|
13
18
|
/** This function gives the words of the outcome of a second request: its connection, a retry, and its end. */
|
|
@@ -166,12 +171,15 @@ const withProgram = (verdict, counted) => {
|
|
|
166
171
|
const outcomes = {};
|
|
167
172
|
for (const call of calls) outcomes[call.outcome] = (outcomes[call.outcome] ?? 0) + 1;
|
|
168
173
|
const gone = lefts.flatMap((each) => each === null ? [] : [each]);
|
|
174
|
+
const ends = counted.flatMap((run) => run.exited === void 0 ? [] : [run.exited]);
|
|
169
175
|
const left = lefts.length === 0 ? void 0 : {
|
|
170
176
|
spread: spreadOf(gone),
|
|
171
|
-
stayed: lefts.length - gone.length
|
|
177
|
+
stayed: lefts.length - gone.length,
|
|
178
|
+
...ends.length === 0 ? {} : { exited: spreadOf(ends) }
|
|
172
179
|
};
|
|
180
|
+
const ended = ends.length === 0 ? "" : `: in ${ends.length} the socket stayed open until the client process ended, median ${ms(spreadOf(ends))} after the drop line`;
|
|
173
181
|
const said = [
|
|
174
|
-
...left === void 0 ? [] : [`After the drop, the program closed its socket in ${gone.length} runs, median ${ms(left.spread)} after its drop line${left.stayed === 0 ? "" : `, and it did not close it in ${left.stayed}`}.`],
|
|
182
|
+
...left === void 0 ? [] : [`After the drop, the program closed its socket in ${gone.length} runs, median ${ms(left.spread)} after its drop line${left.stayed === 0 ? "" : `, and it did not close it in ${left.stayed}${ended}`}.`],
|
|
175
183
|
...reconnect === void 0 ? [] : [`Reconnect: the waits before each try, median ${reconnect.waits.map(ms).join(", ")}; the first SYN median ${ms(reconnect.syn)} and ESTABLISHED median ${ms(reconnect.established)} after the detection.`],
|
|
176
184
|
...calls.length === 0 ? [] : [`Calls in flight: ${Object.entries(outcomes).map(([outcome, n]) => `${n} ${outcome}`).join("; ")}.`]
|
|
177
185
|
];
|
|
@@ -500,15 +508,41 @@ const leftAfter = (socket, line) => {
|
|
|
500
508
|
return left === void 0 ? null : between$3(line.t, left.mono);
|
|
501
509
|
};
|
|
502
510
|
/**
|
|
511
|
+
* This function gives the end of the client process in a run, as the kernel stamps it. The process
|
|
512
|
+
* ends after its last try, and its kernel then closes each socket that is still open, at one
|
|
513
|
+
* instant. Thus the end is the first change out of ESTABLISHED of another socket of the client
|
|
514
|
+
* after its last `dial` line. The program dropped none of these sockets. It is undefined when the
|
|
515
|
+
* record has no such change, for example when the last try did not open.
|
|
516
|
+
*/
|
|
517
|
+
const exitOf = (run, cookie) => {
|
|
518
|
+
const last = run.events.filter((line) => line.ev === "dial").at(-1);
|
|
519
|
+
if (last === void 0) return void 0;
|
|
520
|
+
const dropped = new Set(run.events.filter((line) => line.ev === "drop").map((line) => line["local"]));
|
|
521
|
+
return run.kernel.find((record) => record.netns === run.netns && record.cookie !== cookie && record.ev === "state" && oldState$2(record) === 1 && (newState$2(record) === 4 || newState$2(record) === 7) && record.mono >= last.t && !dropped.has(record.src))?.mono;
|
|
522
|
+
};
|
|
523
|
+
/**
|
|
524
|
+
* This function separates a close of the socket by the program from the end of the client process.
|
|
525
|
+
* `left` is the time from the drop line to the close, and `exit` is the end of the process. A close
|
|
526
|
+
* in δ_abs of that end is the end, not the program: `left` becomes null, and `exited` keeps the time.
|
|
527
|
+
*/
|
|
528
|
+
const closeOrExit = (line, left, exit, delta = NOISE) => {
|
|
529
|
+
if (left === null || exit === void 0) return { left };
|
|
530
|
+
const at = line.t + valueIn(left, "s") * 1e9;
|
|
531
|
+
return Math.abs(at - exit) <= valueIn(delta, "s") * 1e9 ? {
|
|
532
|
+
left: null,
|
|
533
|
+
exited: left
|
|
534
|
+
} : { left };
|
|
535
|
+
};
|
|
536
|
+
/**
|
|
503
537
|
* This function tells how a drop differs from the way that its prediction gives, or gives null.
|
|
504
538
|
* A close closes the socket in δ_abs. A close frame is a write, and the program does not close
|
|
505
539
|
* the socket at the drop. A peer that answers the close frame can close it, and that is no finding.
|
|
506
540
|
*/
|
|
507
|
-
const dropFinding = (expected, drop, left, delta = NOISE) => {
|
|
541
|
+
const dropFinding = (expected, drop, left, delta = NOISE, exited) => {
|
|
508
542
|
const noise = valueIn(delta, "s");
|
|
509
543
|
const soon = left !== null && valueIn(left, "s") <= noise;
|
|
510
544
|
if (expected === "close") {
|
|
511
|
-
if (left === null) return
|
|
545
|
+
if (left === null) return `the program did not close its socket after the drop, where a close is at once${exited === void 0 ? "" : ": it stayed open until the client process ended"}`;
|
|
512
546
|
return soon ? null : `the program closed its socket ${left.value} s after the drop, not at once`;
|
|
513
547
|
}
|
|
514
548
|
if (drop.record !== "write") return "the drop wrote no close frame on its socket";
|
|
@@ -1623,8 +1657,9 @@ const measureSocket = (cell, run) => {
|
|
|
1623
1657
|
const closeWait = socket.find((record) => record.ev === "state" && newState(record) === 8);
|
|
1624
1658
|
const own = cell.prediction.detects === true && cell.prediction.errno === "CLOSED" || cell.prediction.detects === null && cell.prediction.ends === "own-close" ? socket.find((record) => record.ev === "state" && oldState(record) === 1 && newState(record) === 4) : void 0;
|
|
1625
1659
|
const lines = programLines(cell, run);
|
|
1660
|
+
const named = server ? socket.find((record) => record.dst !== void 0)?.dst ?? local : local;
|
|
1626
1661
|
const kernelEnded = reported?.mono ?? closeWait?.mono ?? own?.mono ?? seen?.t;
|
|
1627
|
-
const first = dropOf(lines, socket,
|
|
1662
|
+
const first = dropOf(lines, socket, named, noiseOf(cell));
|
|
1628
1663
|
const drop = first !== void 0 && (kernelEnded === void 0 || first.at <= kernelEnded) ? first : void 0;
|
|
1629
1664
|
const dropped = drop !== void 0;
|
|
1630
1665
|
const outcome = dropped ? { kind: "dropped" } : reported !== void 0 ? {
|
|
@@ -1637,7 +1672,7 @@ const measureSocket = (cell, run) => {
|
|
|
1637
1672
|
const ended = drop?.at ?? kernelEnded;
|
|
1638
1673
|
const noticed = reported !== void 0 && seen !== void 0 && !dropped ? between(reported.mono, seen.t) : null;
|
|
1639
1674
|
const { prediction } = cell;
|
|
1640
|
-
const pings = pingsOf(lines, socket,
|
|
1675
|
+
const pings = pingsOf(lines, socket, named, noiseOf(cell));
|
|
1641
1676
|
const before = run.fault?.t1 ?? ready.t;
|
|
1642
1677
|
const referenceAt = (from) => {
|
|
1643
1678
|
if (from === "request-sent") return request.t;
|
|
@@ -1822,8 +1857,12 @@ const measureSocket = (cell, run) => {
|
|
|
1822
1857
|
const words = outcome.kind === "error" ? outcome.errno : outcome.kind === "closed" ? "its own close" : outcome.kind === "dropped" ? "a drop by its own program" : "end of file";
|
|
1823
1858
|
const reconnect = prediction.reconnects === void 0 ? void 0 : reconnectOf(run, ended, cookie, drop?.line.t ?? ended);
|
|
1824
1859
|
const late = reconnect === void 0 || prediction.reconnects === void 0 ? null : reconnectFinding(prediction.reconnects, reconnect, noiseOf(cell));
|
|
1825
|
-
const
|
|
1826
|
-
const
|
|
1860
|
+
const closed = prediction.drop === void 0 || drop === void 0 ? void 0 : leftAfter(socket, drop.line);
|
|
1861
|
+
const { left, exited } = closed === void 0 || drop === void 0 || prediction.exits !== true ? {
|
|
1862
|
+
left: closed,
|
|
1863
|
+
exited: void 0
|
|
1864
|
+
} : closeOrExit(drop.line, closed, exitOf(run, cookie), noiseOf(cell));
|
|
1865
|
+
const how = prediction.drop === void 0 || drop === void 0 || left === void 0 ? null : dropFinding(prediction.drop, drop, left, noiseOf(cell), exited);
|
|
1827
1866
|
const found = [
|
|
1828
1867
|
matches ? null : `it ended with ${words}, not ${prediction.errno}`,
|
|
1829
1868
|
late,
|
|
@@ -1851,6 +1890,7 @@ const measureSocket = (cell, run) => {
|
|
|
1851
1890
|
steps,
|
|
1852
1891
|
...reconnect === void 0 ? {} : { reconnect },
|
|
1853
1892
|
...left === void 0 ? {} : { left },
|
|
1893
|
+
...exited === void 0 ? {} : { exited },
|
|
1854
1894
|
...cell.probe.ws !== void 0 && inflight ? { call: callOf(events, request, ended) } : {},
|
|
1855
1895
|
void: voided,
|
|
1856
1896
|
finding
|
|
@@ -1913,7 +1953,7 @@ const MONGO = {
|
|
|
1913
1953
|
dockerfile: "harness/image/mongo/Dockerfile",
|
|
1914
1954
|
from: ["harness/image/mongo"]
|
|
1915
1955
|
};
|
|
1916
|
-
/** This image holds `@procedures/client` 0.
|
|
1956
|
+
/** This image holds `@procedures/client` 0.7.0 on Node 24.21.0: its WebSocket client as a client of the harness, and the script of its server. */
|
|
1917
1957
|
const PROCEDURES = {
|
|
1918
1958
|
name: "loom-harness-procedures",
|
|
1919
1959
|
dockerfile: "harness/image/procedures/Dockerfile",
|
|
@@ -4151,7 +4191,7 @@ const droppedSoFar = (records) => {
|
|
|
4151
4191
|
/** This function gives the argv of the client for a cell, with a deadline far after all that it waits for. */
|
|
4152
4192
|
const probeArgs = (cell, lab) => {
|
|
4153
4193
|
const { prediction, probe } = cell;
|
|
4154
|
-
const longest = (prediction.detects === true && prediction.driver !== void 0 ? longestOf(cell) : prediction.detects ?
|
|
4194
|
+
const longest = (prediction.detects === true && prediction.driver !== void 0 ? longestOf(cell) : prediction.detects ? latestEndOf(prediction) : valueIn(prediction.window, "s")) + (cell.idleBefore === void 0 ? 0 : valueIn(cell.idleBefore, "s"));
|
|
4155
4195
|
const args = [
|
|
4156
4196
|
lab.targetIp,
|
|
4157
4197
|
String(lab.targetPort),
|
|
@@ -4521,7 +4561,7 @@ const once = async (lab, cell, environment, index, dry = false) => {
|
|
|
4521
4561
|
"deadline"
|
|
4522
4562
|
], window).catch(() => void 0);
|
|
4523
4563
|
} else if (prediction.detects) {
|
|
4524
|
-
const within =
|
|
4564
|
+
const within = latestEndOf(prediction) * 2e3 + 1e4;
|
|
4525
4565
|
if (cell.observer !== void 0 && cell.observer !== "client") await observerError(cell, partial, within, () => lab.server.slice(serverFrom).some((line) => line.ev === "drop"));
|
|
4526
4566
|
else if (http && cell.probe.mode !== "idle") await probe.until(HTTP_DONE, within).catch(() => void 0);
|
|
4527
4567
|
else await probe.until([
|
|
@@ -4723,6 +4763,6 @@ const runCell = async (cell, say = () => {}) => {
|
|
|
4723
4763
|
}
|
|
4724
4764
|
};
|
|
4725
4765
|
//#endregion
|
|
4726
|
-
export { CONVERGE_MS, INJECT, KEPT, LINGER_MS, LISTING, MEMBER_MARKS, NOISE, PUMP, QUIET_MS, RELATIVE, REPLICA_SET_STAGES, STAGES, STATUS_SCRIPT, STEPS, STEP_DOWN, callOf, clockOffsetOf, crossingsOf, cutOf, deadlineOf, delayVoids, deviceOf, dropFinding, dropOf, endpointAfter, errorsOf, fieldOf, fieldsOf, firstUnanswered, goHttpImage, groupOnce, groupVerdictOf, harnessImage, hzOf, infoOf, initiateScript, isHopPlace, judge, lagsOf, leftAfter, matches, measure, measureDriver, measureGroup, medianInterval, memberCommand, memberConfig, memberLineOf, mongoClientImage, nodeClientImage, nodeHttpImage, noiseOf, onMonotonic, openGroupLab, openLab, openReplicaSetLab, peersOf, pingsOf, proceduresImage, programLines, quantileOf, reconnectFinding, reconnectOf, referenceOf, reorderingOf, replicaSetArgs, replicaSetOnce, replicaSetStagesOf, requestOf, requestWords, routerImage, routesOf, runCell, sentinelConfig, socketAt, socketOf, socketsOf, spread, spreadOf, stagesOf, statusViewOf, subscriptionsOf, targetImageOf, templated, toxicBody, verdictOf, viewOf, writesOf };
|
|
4766
|
+
export { CONVERGE_MS, INJECT, KEPT, LINGER_MS, LISTING, MEMBER_MARKS, NOISE, PUMP, QUIET_MS, RELATIVE, REPLICA_SET_STAGES, STAGES, STATUS_SCRIPT, STEPS, STEP_DOWN, callOf, clockOffsetOf, closeOrExit, crossingsOf, cutOf, deadlineOf, delayVoids, deviceOf, dropFinding, dropOf, endpointAfter, errorsOf, exitOf, fieldOf, fieldsOf, firstUnanswered, goHttpImage, groupOnce, groupVerdictOf, harnessImage, hzOf, infoOf, initiateScript, isHopPlace, judge, lagsOf, latestEndOf, leftAfter, matches, measure, measureDriver, measureGroup, medianInterval, memberCommand, memberConfig, memberLineOf, mongoClientImage, nodeClientImage, nodeHttpImage, noiseOf, onMonotonic, openGroupLab, openLab, openReplicaSetLab, peersOf, pingsOf, proceduresImage, programLines, quantileOf, reconnectFinding, reconnectOf, referenceOf, reorderingOf, replicaSetArgs, replicaSetOnce, replicaSetStagesOf, requestOf, requestWords, routerImage, routesOf, runCell, sentinelConfig, socketAt, socketOf, socketsOf, spread, spreadOf, stagesOf, statusViewOf, subscriptionsOf, targetImageOf, templated, toxicBody, verdictOf, viewOf, writesOf };
|
|
4727
4767
|
|
|
4728
4768
|
//# sourceMappingURL=index.js.map
|