@mark1russell7/harness 0.11.4 → 0.11.6

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/build/schema.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { J as Subject, _ as ImageSource, i as ClientImage, r as Client } from "./cell-Di_YHiuB.js";
1
+ import { J as Subject, _ as ImageSource, i as ClientImage, r as Client } from "./cell-lSCgFB54.js";
2
2
  import { z } from "zod";
3
3
  //#region src/schema.d.ts
4
4
  /**
@@ -16,7 +16,8 @@
16
16
  // - ready: the reply to the first query: the connection is idle in the pool (idle, inflight), or
17
17
  // the stream starts (stream).
18
18
  // - sent, ack, fail: each query by its sequence number, with its error.
19
- // - data, error: the end of the query in flight, as the TCP client of the harness tells it.
19
+ // - data, error: the end of the query in flight, as the TCP client of the harness tells it. In idle
20
+ // mode, the close of the socket of the first connection is an error line too: the pool closed it.
20
21
  // - done, deadline: the end of the client.
21
22
  //
22
23
  // The modes are those of the pg client. idle sends one query, closes its connection (the
@@ -60,6 +61,7 @@ public final class ClientHikari {
60
61
  private static volatile boolean stopped = false;
61
62
  private static volatile HikariDataSource pool;
62
63
  private static volatile String connected;
64
+ private static volatile Socket observed;
63
65
  private static String host;
64
66
  private static String port;
65
67
  private static String query;
@@ -216,8 +218,8 @@ public final class ClientHikari {
216
218
 
217
219
  // ---- each connection that the client borrows: its socket, read from pgjdbc
218
220
 
219
- /** This function gives the local address of the socket of a connection, or null. */
220
- private static String localOf(Connection connection) {
221
+ /** This function gives the socket of a connection, from the PGStream of pgjdbc, or null. */
222
+ private static Socket socketOf(Connection connection) {
221
223
  try {
222
224
  PgConnection pg = connection.unwrap(PgConnection.class);
223
225
  Object executor = pg.getQueryExecutor();
@@ -231,23 +233,50 @@ public final class ClientHikari {
231
233
  }
232
234
  if (field == null) return null;
233
235
  field.setAccessible(true);
234
- Socket socket = ((PGStream) field.get(executor)).getSocket();
235
- return socket.getLocalAddress().getHostAddress() + ":" + socket.getLocalPort();
236
+ return ((PGStream) field.get(executor)).getSocket();
236
237
  } catch (Exception error) {
237
238
  return null;
238
239
  }
239
240
  }
240
241
 
241
242
  private static void seen(Connection connection) {
242
- String local = localOf(connection);
243
- if (local == null || !SEEN.add(local)) return;
243
+ Socket socket = socketOf(connection);
244
+ if (socket == null) return;
245
+ String local = socket.getLocalAddress().getHostAddress() + ":" + socket.getLocalPort();
246
+ if (!SEEN.add(local)) return;
244
247
  emit("socket", "\"role\":\"pool\",\"local\":" + q(local));
245
248
  if (connected == null) {
246
249
  connected = local;
250
+ observed = socket;
247
251
  emit("connected", "\"local\":" + q(local));
248
252
  }
249
253
  }
250
254
 
255
+ /**
256
+ * This function watches the socket of the first connection in idle mode, each 2 ms. The pool
257
+ * closes it when a check finds it dead: pgjdbc closes the socket after a read timeout or an I/O
258
+ * error. That close is the detection of an idle cell, thus it writes an error line.
259
+ */
260
+ private static void watchIdle() {
261
+ Socket socket = observed;
262
+ if (socket == null) return;
263
+ Thread watcher = new Thread(() -> {
264
+ while (!stopped) {
265
+ if (socket.isClosed()) {
266
+ emit("error", "\"errno\":\"CLOSED\",\"local\":" + q(connected));
267
+ return;
268
+ }
269
+ try {
270
+ Thread.sleep(2);
271
+ } catch (InterruptedException stop) {
272
+ return;
273
+ }
274
+ }
275
+ }, "idle-watcher");
276
+ watcher.setDaemon(true);
277
+ watcher.start();
278
+ }
279
+
251
280
  // ---- the counts of the pool, each 5 ms: a line for each change
252
281
 
253
282
  private static void watchPool() {
@@ -332,6 +361,35 @@ public final class ClientHikari {
332
361
  }
333
362
  }
334
363
 
364
+ /**
365
+ * This function loads the code of a connect and of the start of a pool before the first line, on
366
+ * the loopback of the namespace. A new JVM loads each class at its first use, and that load would
367
+ * be in the band of a cell that starts into a fault. A pool with no check and no idle connection
368
+ * makes no connection: it loads the classes of the pool. It also sets the login timeout of
369
+ * DriverManager, thus pgjdbc then connects in a task of an executor (Driver.java:303-311,
370
+ * :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 (PoolBase.java:363), and the
372
+ * first one starts the SecureRandom of the JVM.
373
+ */
374
+ private static void warmUp() {
375
+ HikariConfig empty = new HikariConfig();
376
+ empty.setJdbcUrl("jdbc:postgresql://127.0.0.1:1/warmup");
377
+ empty.setUsername("warmup");
378
+ empty.setPoolName("warmup");
379
+ empty.setInitializationFailTimeout(-1);
380
+ empty.setMinimumIdle(0);
381
+ // the pool starts and closes with no connection
382
+ new HikariDataSource(empty).close();
383
+ Properties refused = new Properties();
384
+ refused.setProperty("user", "warmup");
385
+ try {
386
+ new org.postgresql.Driver().connect("jdbc:postgresql://127.0.0.1:1/warmup", refused);
387
+ } catch (SQLException expected) {
388
+ // the refusal is the purpose of the connect
389
+ }
390
+ java.util.UUID.randomUUID();
391
+ }
392
+
335
393
  public static void main(String[] args) throws Exception {
336
394
  List<String> argv = List.of(args);
337
395
  String mode = flag(argv, "--mode", null);
@@ -347,6 +405,7 @@ public final class ClientHikari {
347
405
  query = flag(argv, "--query", "SELECT 1");
348
406
  boolean before = argv.contains("--before");
349
407
  Map<String, String> options = parseObject(flag(argv, "--options", "{}"));
408
+ warmUp();
350
409
 
351
410
  String bootId;
352
411
  try {
@@ -413,6 +472,7 @@ public final class ClientHikari {
413
472
  }
414
473
  // the connection went back to the pool at the end of the query: it is idle there
415
474
  emit("ready", "\"reply\":\"ack\"");
475
+ if (mode.equals("idle")) watchIdle();
416
476
  if (mode.equals("inflight")) {
417
477
  while (!Files.exists(go)) Thread.sleep(2);
418
478
  Throwable error = operate(2);
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",
4
+ "version": "0.11.6",
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",