@mark1russell7/harness 0.15.1 → 0.15.3

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 { A as ImageSource, At as Subject, i as Client, o as ClientImage } from "./cell-biffSiEJ.js";
1
+ import { i as Client, j as ImageSource, jt as Subject, o as ClientImage } from "./cell-BmqDPjz3.js";
2
2
  import { z } from "zod";
3
3
  //#region src/schema.d.ts
4
4
  /**
package/build/schema.js CHANGED
@@ -1,4 +1,4 @@
1
- import { D as subjectProblems, a as SHIPPED_NAMES, h as imageProblems, t as CLIENT_NAMES, z as CLIENT_CAPABILITIES } from "./image-BxVTFZ1o.js";
1
+ import { D as subjectProblems, a as SHIPPED_NAMES, h as imageProblems, t as CLIENT_NAMES, z as CLIENT_CAPABILITIES } from "./image-B8yh_FF7.js";
2
2
  import { z } from "zod";
3
3
  //#region src/schema.ts
4
4
  /** This function adds each problem that a check finds as an issue of a schema. */
@@ -150,6 +150,16 @@ fn wait_for(fd: i32, writing: bool, ns: u64) -> std::io::Result<(bool, bool)> {
150
150
  }
151
151
  }
152
152
 
153
+ /// Gives the time of the next wait of the schedule mode: up to the next arrival, and one tick at most.
154
+ /// A full socket cannot take the arrival that is due. Thus the wait is then for POLLOUT, for one tick.
155
+ /// Without this, an overdue arrival gives a wait of 0, and the loop spins while the socket is full.
156
+ fn wait_ns(blocked: bool, next_at: Option<&u64>, start: u64, now: u64, tick: u64) -> u64 {
157
+ match next_at {
158
+ Some(at) if !blocked => (start + at).saturating_sub(now).min(tick),
159
+ _ => tick,
160
+ }
161
+ }
162
+
153
163
  /// Sends each arrival of `plan` at its time after the file --go appears, whatever the replies do.
154
164
  /// The stamp of a send comes before the write that ends it. Thus a full socket shows as the lag of
155
165
  /// each arrival behind it.
@@ -197,8 +207,7 @@ fn open_loop(stream: &mut TcpStream, plan: &[u64], args: &Args, deadline: u64) -
197
207
  emit("deadline", json!({}));
198
208
  return Ok(());
199
209
  }
200
- let due = plan.get(next).map_or(tick, |at| (start + at).saturating_sub(now));
201
- let (can_read, can_write) = wait_for(fd, blocked, due.min(tick))?;
210
+ let (can_read, can_write) = wait_for(fd, blocked, wait_ns(blocked, plan.get(next), start, now, tick))?;
202
211
  blocked = blocked && !can_write;
203
212
  if !can_read {
204
213
  continue;
@@ -455,4 +464,57 @@ mod tests {
455
464
  assert_eq!(lengths, [7, 9, 7], "reads of {size}");
456
465
  }
457
466
  }
467
+
468
+ #[test]
469
+ fn the_wait_is_one_tick_while_the_socket_is_full() {
470
+ let (start, tick) = (1_000, 10_000_000);
471
+ // an arrival in the future: the wait ends at it
472
+ assert_eq!(wait_ns(false, Some(&5_000), start, 2_000, tick), 4_000);
473
+ // an overdue arrival with room in the socket: no wait
474
+ assert_eq!(wait_ns(false, Some(&0), start, 2_000, tick), 0);
475
+ // an overdue arrival and a full socket: a wait for POLLOUT, not a wait of 0
476
+ assert_eq!(wait_ns(true, Some(&0), start, 2_000, tick), tick);
477
+ // no arrival left: one tick
478
+ assert_eq!(wait_ns(false, None, start, 2_000, tick), tick);
479
+ }
480
+
481
+ fn thread_cpu_ns() -> u64 {
482
+ let mut time = libc::timespec { tv_sec: 0, tv_nsec: 0 };
483
+ // SAFETY: one timespec on this stack
484
+ unsafe { libc::clock_gettime(libc::CLOCK_THREAD_CPUTIME_ID, &mut time) };
485
+ time.tv_sec as u64 * 1_000_000_000 + time.tv_nsec as u64
486
+ }
487
+
488
+ #[test]
489
+ fn a_full_socket_does_not_spin_the_schedule_mode() {
490
+ // a peer that accepts and does not read: the socket of the client fills, and the arrivals stay overdue
491
+ let listener = std::net::TcpListener::bind("127.0.0.1:0").unwrap();
492
+ let mut stream = TcpStream::connect(listener.local_addr().unwrap()).unwrap();
493
+ let _peer = listener.accept().unwrap();
494
+ stream.set_nonblocking(true).unwrap();
495
+ let go = std::env::temp_dir().join(format!("client-go-{}", std::process::id()));
496
+ std::fs::write(&go, b"").unwrap();
497
+ let args = Args {
498
+ host: "127.0.0.1".into(),
499
+ port: 0,
500
+ inflight: false,
501
+ stream: None,
502
+ interval: Duration::ZERO,
503
+ keepalive: false,
504
+ user_timeout_ms: None,
505
+ go: go.to_string_lossy().into_owned(),
506
+ sample: Duration::from_millis(10),
507
+ max: Duration::from_secs(1),
508
+ request: vec![b'x'; 1 << 20],
509
+ schedule: None,
510
+ };
511
+ let plan = vec![0u64; 1_000];
512
+ let length = Duration::from_millis(400).as_nanos() as u64;
513
+ let before = thread_cpu_ns();
514
+ open_loop(&mut stream, &plan, &args, monotonic_ns() + length).unwrap();
515
+ let used = thread_cpu_ns() - before;
516
+ std::fs::remove_file(&go).ok();
517
+ // a spin takes the CPU for all of the 400 ms: a wait of one tick takes a small part of it
518
+ assert!(used < length / 4, "the schedule mode used {used} ns of CPU in {length} ns");
519
+ }
458
520
  }
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.15.1",
4
+ "version": "0.15.3",
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",