@mark1russell7/harness 0.15.0 → 0.15.2
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/{cell-biffSiEJ.d.ts → cell-OwQdpSkd.d.ts} +9 -2
- package/build/{image-BxVTFZ1o.js → image-B8yh_FF7.js} +9 -2
- package/build/{image-BxVTFZ1o.js.map → image-B8yh_FF7.js.map} +1 -1
- package/build/index.d.ts +61 -4
- package/build/index.js +134 -25
- package/build/index.js.map +1 -1
- package/build/schema.d.ts +1 -1
- package/build/schema.js +1 -1
- package/image/native/src/bin/client.rs +64 -2
- package/package.json +2 -2
package/build/schema.d.ts
CHANGED
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-
|
|
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
|
|
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.
|
|
4
|
+
"version": "0.15.2",
|
|
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",
|
|
@@ -108,7 +108,7 @@
|
|
|
108
108
|
"@mark1russell7/prober": "^0.3.2",
|
|
109
109
|
"@mark1russell7/quantity": "^0.3.0",
|
|
110
110
|
"@mark1russell7/rigor-bench": "^0.2.10",
|
|
111
|
-
"@mark1russell7/rigor-chaos": "^0.1.
|
|
111
|
+
"@mark1russell7/rigor-chaos": "^0.1.3",
|
|
112
112
|
"@mark1russell7/rigor-statistics": "^0.1.11",
|
|
113
113
|
"testcontainers": "12.2.0"
|
|
114
114
|
},
|