Skip to content

Commit 45fa303

Browse files
committed
chore(antithesis): Parallel drivers retry sends
We modify parallel driver to tolerate both backpressure and network faults. The goal of this work is to ensure we do not delver to one SUT and not the other. All payloads rip through or the driver fails loudly.
1 parent 349aec9 commit 45fa303

2 files changed

Lines changed: 40 additions & 20 deletions

File tree

test/antithesis/harness/src/driver.rs

Lines changed: 39 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,15 @@
55
//! differ only in how many sockets they target and which anchors they fire, so
66
//! both the single-socket and differential drivers run on this one engine.
77
8+
use std::io::ErrorKind;
89
use std::os::unix::net::UnixDatagram;
910
use std::path::Path;
1011
use std::sync::mpsc::sync_channel;
1112
use std::thread::{self, sleep};
1213
use std::time::{Duration, Instant};
1314

1415
use antithesis_sdk::random::{random_choice, AntithesisRng};
16+
use anyhow::Context;
1517
use rand::{rand_core::UnwrapErr, RngExt};
1618

1719
use crate::payload::dogstatsd;
@@ -82,17 +84,16 @@ pub struct Stats {
8284
pub max_packed: Vec<usize>,
8385
}
8486

85-
/// Drive one batch of sampled `DogStatsD` lines to every socket.
86-
///
87-
/// A producer samples up to ~10k lines at the batch's vibe and queues them; a
88-
/// consumer ships each line to every socket and tallies per-socket sends. The
89-
/// returned [`Stats`] anchor the caller's assertions.
87+
const SEND_RETRY_BUDGET: Duration = Duration::from_secs(5);
88+
const SEND_RETRY_BACKOFF: Duration = Duration::from_millis(1);
89+
90+
/// Drive one batch of sampled `DogStatsD` lines to every socket, blocking through
91+
/// backpressure so on success `sent[i] == received` for all `i`.
9092
///
91-
/// # Panics
93+
/// # Errors
9294
///
93-
/// Panics if the producer or consumer thread panics.
94-
#[must_use]
95-
pub fn run(batch: Batch, sockets: Vec<UnixDatagram>) -> Stats {
95+
/// Errors if a line cannot be delivered within the retry budget, or a worker panics.
96+
pub fn run(batch: Batch, sockets: Vec<UnixDatagram>) -> anyhow::Result<Stats> {
9697
let count = {
9798
let mut rng = UnwrapErr(AntithesisRng);
9899
rng.random_range(0..=10_000u64)
@@ -114,30 +115,49 @@ pub fn run(batch: Batch, sockets: Vec<UnixDatagram>) -> Stats {
114115
}
115116
});
116117

117-
let consumer = thread::spawn(move || {
118+
let consumer = thread::spawn(move || -> anyhow::Result<Stats> {
118119
let mut received = 0usize;
119120
let mut sent = vec![0usize; sockets.len()];
120121
let mut max_packed = vec![0usize; sockets.len()];
121122
while let Ok(line) = rx.recv() {
122123
received += 1;
123124
for (i, socket) in sockets.iter().enumerate() {
124-
if socket.send(line.bytes()).is_ok() {
125-
sent[i] += 1;
126-
if let Line::Multi { count, .. } = &line {
127-
max_packed[i] = max_packed[i].max(*count);
128-
}
125+
deliver(socket, line.bytes())?;
126+
sent[i] += 1;
127+
if let Line::Multi { count, .. } = &line {
128+
max_packed[i] = max_packed[i].max(*count);
129129
}
130130
}
131131
}
132-
Stats {
132+
Ok(Stats {
133133
received,
134134
sent,
135135
max_packed,
136-
}
136+
})
137137
});
138138

139-
producer.join().expect("producer thread panicked");
140-
consumer.join().expect("consumer thread panicked")
139+
producer
140+
.join()
141+
.map_err(|_| anyhow::anyhow!("producer thread panicked"))?;
142+
consumer
143+
.join()
144+
.map_err(|_| anyhow::anyhow!("consumer thread panicked"))?
145+
}
146+
147+
fn deliver(socket: &UnixDatagram, bytes: &[u8]) -> anyhow::Result<()> {
148+
let deadline = Instant::now() + SEND_RETRY_BUDGET;
149+
loop {
150+
match socket.send(bytes) {
151+
Ok(_) => return Ok(()),
152+
Err(e) if is_transient(&e) && Instant::now() < deadline => sleep(SEND_RETRY_BACKOFF),
153+
Err(e) => return Err(e).context("deliver line to socket"),
154+
}
155+
}
156+
}
157+
158+
fn is_transient(error: &std::io::Error) -> bool {
159+
const ENOBUFS: i32 = 105;
160+
matches!(error.kind(), ErrorKind::WouldBlock | ErrorKind::Interrupted) || error.raw_os_error() == Some(ENOBUFS)
141161
}
142162

143163
/// Wait for the remote process to bind `path`, intentionally naive. Returns

test/antithesis/scenarios/general/src/bin/parallel_driver_send_dogstatsd.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ mod unix_driver {
3333
};
3434

3535
let batch = Batch::sample();
36-
let stats = driver::run(batch, vec![socket]);
36+
let stats = driver::run(batch, vec![socket])?;
3737
let sent = stats.sent[0];
3838
let max_packed = stats.max_packed[0];
3939

0 commit comments

Comments
 (0)