@mark1russell7/harness 0.11.6 → 0.11.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 +7 -4
- package/build/{cell-lSCgFB54.d.ts → cell-7HFQIHlK.d.ts} +10 -3
- package/build/index.d.ts +9 -1
- package/build/index.js +13 -1
- package/build/index.js.map +1 -1
- package/build/schema.d.ts +1 -1
- package/image/hikari/ClientHikari.java +24 -8
- package/image/pg/client-pg.mjs +10 -6
- package/image/router/ctwatch.py +4 -3
- package/package.json +1 -1
package/build/schema.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// client-hikari HOST PORT --mode idle|inflight|stream [--interval-ms N] [--max-s N] [--go PATH]
|
|
2
|
-
// [--query TEXT] [--before] [--options JSON]
|
|
2
|
+
// [--query TEXT] [--inflight-query TEXT] [--before] [--options JSON]
|
|
3
3
|
//
|
|
4
4
|
// The client of a HikariCP cell: HikariCP 7.1.0 over pgjdbc 42.7.13, on Java 21. Each event is a
|
|
5
5
|
// JSON line with `ev` and `t`, the time on CLOCK_MONOTONIC in nanoseconds (System.nanoTime, which
|
|
@@ -24,7 +24,8 @@
|
|
|
24
24
|
// connection goes back to the pool), then holds until --max-s. inflight sends one query, waits for
|
|
25
25
|
// the file --go, sends one more query into the fault, and holds after its end. stream sends a query
|
|
26
26
|
// each --interval-ms after the reply before. --before starts the client into a fault that holds
|
|
27
|
-
// already: the start of its pool meets it.
|
|
27
|
+
// already: the start of its pool meets it. --inflight-query is the text of the query in flight
|
|
28
|
+
// (query 2 of inflight), and --query is the text of each other query.
|
|
28
29
|
//
|
|
29
30
|
// The options are one flat JSON object. A name with the prefix `dataSource.` is a property of
|
|
30
31
|
// pgjdbc. Each other name is a property of HikariConfig. A cell gives each option that it changes,
|
|
@@ -53,6 +54,8 @@ import java.util.Set;
|
|
|
53
54
|
import java.util.concurrent.ConcurrentHashMap;
|
|
54
55
|
import org.postgresql.core.PGStream;
|
|
55
56
|
import org.postgresql.jdbc.PgConnection;
|
|
57
|
+
import org.postgresql.util.PSQLException;
|
|
58
|
+
import org.postgresql.util.ServerErrorMessage;
|
|
56
59
|
|
|
57
60
|
public final class ClientHikari {
|
|
58
61
|
private static final PrintStream OUT = new PrintStream(new FileOutputStream(FileDescriptor.out), false, StandardCharsets.UTF_8);
|
|
@@ -65,6 +68,7 @@ public final class ClientHikari {
|
|
|
65
68
|
private static String host;
|
|
66
69
|
private static String port;
|
|
67
70
|
private static String query;
|
|
71
|
+
private static String inflightQuery;
|
|
68
72
|
private static HikariConfig config;
|
|
69
73
|
|
|
70
74
|
private ClientHikari() {}
|
|
@@ -334,8 +338,13 @@ public final class ClientHikari {
|
|
|
334
338
|
Runtime.getRuntime().halt(code);
|
|
335
339
|
}
|
|
336
340
|
|
|
337
|
-
/** This function sends query n through the pool. Its stamp is before the borrow. It gives null for a reply, or the error. */
|
|
341
|
+
/** This function sends query n through the pool, with the text of each query. Its stamp is before the borrow. It gives null for a reply, or the error. */
|
|
338
342
|
private static Throwable operate(int n) {
|
|
343
|
+
return operate(n, query);
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
/** This function sends query n through the pool, with its text. Its stamp is before the borrow. It gives null for a reply, or the error. */
|
|
347
|
+
private static Throwable operate(int n, String text) {
|
|
339
348
|
long at = now();
|
|
340
349
|
if (n == 1) emit("connecting", at, "\"at\":" + q(host + ":" + port));
|
|
341
350
|
emit("sent", at, "\"n\":" + n);
|
|
@@ -346,7 +355,7 @@ public final class ClientHikari {
|
|
|
346
355
|
try (Connection connection = pool.getConnection()) {
|
|
347
356
|
seen(connection);
|
|
348
357
|
try (Statement statement = connection.createStatement()) {
|
|
349
|
-
if (statement.execute(
|
|
358
|
+
if (statement.execute(text)) {
|
|
350
359
|
try (ResultSet result = statement.getResultSet()) {
|
|
351
360
|
while (result.next()) rows += 1;
|
|
352
361
|
}
|
|
@@ -368,8 +377,10 @@ public final class ClientHikari {
|
|
|
368
377
|
* makes no connection: it loads the classes of the pool. It also sets the login timeout of
|
|
369
378
|
* DriverManager, thus pgjdbc then connects in a task of an executor (Driver.java:303-311,
|
|
370
379
|
* :730-739). A connect to port 1 of the loopback after it is refused at once: it loads that
|
|
371
|
-
* connect of pgjdbc and its errors. HikariCP gives each new connection a random UUID
|
|
372
|
-
* first one starts the SecureRandom of the JVM.
|
|
380
|
+
* connect of pgjdbc and its errors. HikariCP gives each new connection a random UUID
|
|
381
|
+
* (PoolBase.java:363), and the first one starts the SecureRandom of the JVM. An error of the
|
|
382
|
+
* server that pgjdbc parses loads the classes of an ERROR of a statement, for example of its
|
|
383
|
+
* timeout.
|
|
373
384
|
*/
|
|
374
385
|
private static void warmUp() {
|
|
375
386
|
HikariConfig empty = new HikariConfig();
|
|
@@ -388,6 +399,10 @@ public final class ClientHikari {
|
|
|
388
399
|
// the refusal is the purpose of the connect
|
|
389
400
|
}
|
|
390
401
|
java.util.UUID.randomUUID();
|
|
402
|
+
// the fields of an ErrorResponse, each a code and a text with a NUL after it
|
|
403
|
+
PSQLException cancelled = new PSQLException(new ServerErrorMessage("SERROR\0VERROR\0C57014\0Mwarm-up\0"));
|
|
404
|
+
errnoOf(cancelled);
|
|
405
|
+
failure(cancelled);
|
|
391
406
|
}
|
|
392
407
|
|
|
393
408
|
public static void main(String[] args) throws Exception {
|
|
@@ -403,6 +418,7 @@ public final class ClientHikari {
|
|
|
403
418
|
long max = Long.parseLong(flag(argv, "--max-s", "120")) * 1000;
|
|
404
419
|
Path go = Path.of(flag(argv, "--go", "/tmp/go"));
|
|
405
420
|
query = flag(argv, "--query", "SELECT 1");
|
|
421
|
+
inflightQuery = flag(argv, "--inflight-query", query);
|
|
406
422
|
boolean before = argv.contains("--before");
|
|
407
423
|
Map<String, String> options = parseObject(flag(argv, "--options", "{}"));
|
|
408
424
|
warmUp();
|
|
@@ -424,7 +440,7 @@ public final class ClientHikari {
|
|
|
424
440
|
+ ",\"client\":\"hikari\",\"driver\":" + q(org.postgresql.util.DriverInfo.DRIVER_VERSION)
|
|
425
441
|
+ ",\"pool\":" + q(hikari == null ? "7.1.0" : hikari)
|
|
426
442
|
+ ",\"java\":" + q(System.getProperty("java.version"))
|
|
427
|
-
+ ",\"mode\":" + q(mode) + ",\"query\":" + q(query) + ",\"options\":" + given);
|
|
443
|
+
+ ",\"mode\":" + q(mode) + ",\"query\":" + q(query) + ",\"inflight_query\":" + q(inflightQuery) + ",\"options\":" + given);
|
|
428
444
|
|
|
429
445
|
Properties properties = new Properties();
|
|
430
446
|
for (Map.Entry<String, String> each : options.entrySet()) properties.setProperty(each.getKey(), each.getValue());
|
|
@@ -475,7 +491,7 @@ public final class ClientHikari {
|
|
|
475
491
|
if (mode.equals("idle")) watchIdle();
|
|
476
492
|
if (mode.equals("inflight")) {
|
|
477
493
|
while (!Files.exists(go)) Thread.sleep(2);
|
|
478
|
-
Throwable error = operate(2);
|
|
494
|
+
Throwable error = operate(2, inflightQuery);
|
|
479
495
|
if (error == null) emit("data", "\"n\":2");
|
|
480
496
|
else emit("error", "\"errno\":" + q(errnoOf(error)) + ",\"message\":" + q(error.getMessage()));
|
|
481
497
|
}
|
package/image/pg/client-pg.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// client-pg HOST PORT --mode idle|inflight|stream [--interval-ms N] [--max-s N] [--go PATH]
|
|
2
|
-
// [--query TEXT] [--before] [--options JSON]
|
|
2
|
+
// [--query TEXT] [--inflight-query TEXT] [--before] [--options JSON]
|
|
3
3
|
//
|
|
4
4
|
// The client of a PostgreSQL cell: node-postgres (pg 8.23.1 with pg-pool 3.14.0) on Node 24.21.0.
|
|
5
5
|
// Each event is a JSON line with `ev` and `t`, the time on CLOCK_MONOTONIC in nanoseconds
|
|
@@ -24,6 +24,9 @@
|
|
|
24
24
|
// inflight sends one query, waits for the file --go, sends one more query into the fault, and holds
|
|
25
25
|
// after its end. stream sends a query each --interval-ms after the reply before, until --max-s or
|
|
26
26
|
// SIGTERM. --before starts the client into a fault that holds already: its first connect meets it.
|
|
27
|
+
// --inflight-query is the text of the query in flight (query 2 of inflight), and --query is the
|
|
28
|
+
// text of each other query. Thus a slow query in flight does not delay the reply that makes the
|
|
29
|
+
// client ready.
|
|
27
30
|
|
|
28
31
|
import { readFileSync } from "node:fs";
|
|
29
32
|
import { existsSync } from "node:fs";
|
|
@@ -46,6 +49,7 @@ const interval = Number(flag("--interval-ms") ?? 50);
|
|
|
46
49
|
const max = Number(flag("--max-s") ?? 120) * 1000;
|
|
47
50
|
const go = flag("--go") ?? "/tmp/go";
|
|
48
51
|
const query = flag("--query") ?? "SELECT 1";
|
|
52
|
+
const inflightQuery = flag("--inflight-query") ?? query;
|
|
49
53
|
const before = argv.includes("--before");
|
|
50
54
|
// the options of the pool. A cell gives each option that it changes, and pg-pool gives the others
|
|
51
55
|
const options = JSON.parse(flag("--options") ?? "{}");
|
|
@@ -60,7 +64,7 @@ const bootId = () => {
|
|
|
60
64
|
return null;
|
|
61
65
|
}
|
|
62
66
|
};
|
|
63
|
-
emit("start", { boot_id: bootId(), client: "pg", driver: versionOf("pg"), pool: versionOf("pg-pool"), node: process.versions.node, mode, query, options });
|
|
67
|
+
emit("start", { boot_id: bootId(), client: "pg", driver: versionOf("pg"), pool: versionOf("pg-pool"), node: process.versions.node, mode, query, inflight_query: inflightQuery, options });
|
|
64
68
|
|
|
65
69
|
const pool = new pg.Pool({ host, port: Number(port), user: "postgres", database: "postgres", ...options });
|
|
66
70
|
|
|
@@ -116,10 +120,10 @@ setTimeout(() => {
|
|
|
116
120
|
stop(0);
|
|
117
121
|
}, max);
|
|
118
122
|
|
|
119
|
-
/** This function sends query n through the pool. Its stamp is before the call. It gives null for a reply, or the error. */
|
|
120
|
-
const operate = async (n) => {
|
|
123
|
+
/** This function sends query n through the pool, with the text of n. Its stamp is before the call. It gives null for a reply, or the error. */
|
|
124
|
+
const operate = async (n, text = query) => {
|
|
121
125
|
const at = now();
|
|
122
|
-
const reply = pool.query(
|
|
126
|
+
const reply = pool.query(text);
|
|
123
127
|
// a band of a cell that starts into a fault counts from the start of the first connect
|
|
124
128
|
if (n === 1) emit("connecting", { at: `${host}:${port}` }, at);
|
|
125
129
|
emit("sent", { n }, at);
|
|
@@ -158,7 +162,7 @@ if (mode === "stream") {
|
|
|
158
162
|
resolve();
|
|
159
163
|
}, 2);
|
|
160
164
|
});
|
|
161
|
-
const error = await operate(2);
|
|
165
|
+
const error = await operate(2, inflightQuery);
|
|
162
166
|
if (error === null) emit("data", { n: 2 });
|
|
163
167
|
else emit("error", { errno: errnoOf(error), message: error.message });
|
|
164
168
|
}
|
package/image/router/ctwatch.py
CHANGED
|
@@ -4,9 +4,10 @@
|
|
|
4
4
|
Each line is {"ev": "ct", "t": <CLOCK_MONOTONIC ns>, "type": "NEW|UPDATE|DESTROY", "state": ...,
|
|
5
5
|
"orig": "src:sport>dst:dport", "reply": "..."}. The stamp is the time that this process read the
|
|
6
6
|
event, which is within a millisecond of the kernel. A listing every PUMP seconds evicts the expired
|
|
7
|
-
entries that it walks past, thus a DESTROY follows an expiry within PUMP plus two listings
|
|
8
|
-
|
|
9
|
-
|
|
7
|
+
entries that it walks past, thus a DESTROY follows an expiry within PUMP plus two listings, and
|
|
8
|
+
the slack of the sleep and of the read (PUMP_SLACK of the harness, 5 ms). A listing that takes
|
|
9
|
+
longer than SLOW seconds writes a "pump-slow" line with its start and its length: near it, that
|
|
10
|
+
bound of the lag does not hold.
|
|
10
11
|
|
|
11
12
|
ctwatch.py [--pump SECONDS] [--slow SECONDS]
|
|
12
13
|
"""
|
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.8",
|
|
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",
|