44//! fans each line out to every socket and tallies per-socket sends. Drivers
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.
7+ //!
8+ //! NOTE: this driver intentionally blocks on backpressure from the SUT. Retry
9+ //! and backoff timers are meant to endure transient errors.
710
11+ use std:: io:: ErrorKind ;
812use std:: os:: unix:: net:: UnixDatagram ;
913use std:: path:: Path ;
1014use std:: sync:: mpsc:: sync_channel;
1115use std:: thread:: { self , sleep} ;
1216use std:: time:: { Duration , Instant } ;
1317
1418use antithesis_sdk:: random:: { random_choice, AntithesisRng } ;
19+ use anyhow:: Context ;
1520use rand:: { rand_core:: UnwrapErr , RngExt } ;
1621
1722use crate :: payload:: dogstatsd;
1823
24+ const SEND_RETRY_BUDGET : Duration = Duration :: from_secs ( 5 ) ;
25+ const SEND_RETRY_BACKOFF : Duration = Duration :: from_millis ( 1 ) ;
26+
1927/// Per-batch composition: 50% clean, 25% feral, 25% mixed.
2028#[ derive( Clone , Copy , Debug ) ]
2129pub enum Batch {
@@ -82,17 +90,13 @@ pub struct Stats {
8290 pub max_packed : Vec < usize > ,
8391}
8492
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.
93+ /// Drive one batch of sampled `DogStatsD` lines to every socket, blocking through
94+ /// backpressure so on success `sent[i] == received` for all `i`.
9095///
91- /// # Panics
96+ /// # Errors
9297///
93- /// Panics if the producer or consumer thread panics.
94- #[ must_use]
95- pub fn run ( batch : Batch , sockets : Vec < UnixDatagram > ) -> Stats {
98+ /// Errors if a line cannot be delivered within the retry budget, or a worker panics.
99+ pub fn run ( batch : Batch , sockets : Vec < UnixDatagram > ) -> anyhow:: Result < Stats > {
96100 let count = {
97101 let mut rng = UnwrapErr ( AntithesisRng ) ;
98102 rng. random_range ( 0 ..=10_000u64 )
@@ -114,30 +118,49 @@ pub fn run(batch: Batch, sockets: Vec<UnixDatagram>) -> Stats {
114118 }
115119 } ) ;
116120
117- let consumer = thread:: spawn ( move || {
121+ let consumer = thread:: spawn ( move || -> anyhow :: Result < Stats > {
118122 let mut received = 0usize ;
119123 let mut sent = vec ! [ 0usize ; sockets. len( ) ] ;
120124 let mut max_packed = vec ! [ 0usize ; sockets. len( ) ] ;
121125 while let Ok ( line) = rx. recv ( ) {
122126 received += 1 ;
123127 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- }
128+ deliver ( socket, line. bytes ( ) ) ?;
129+ sent[ i] += 1 ;
130+ if let Line :: Multi { count, .. } = & line {
131+ max_packed[ i] = max_packed[ i] . max ( * count) ;
129132 }
130133 }
131134 }
132- Stats {
135+ Ok ( Stats {
133136 received,
134137 sent,
135138 max_packed,
136- }
139+ } )
137140 } ) ;
138141
139- producer. join ( ) . expect ( "producer thread panicked" ) ;
140- consumer. join ( ) . expect ( "consumer thread panicked" )
142+ producer
143+ . join ( )
144+ . map_err ( |_| anyhow:: anyhow!( "producer thread panicked" ) ) ?;
145+ consumer
146+ . join ( )
147+ . map_err ( |_| anyhow:: anyhow!( "consumer thread panicked" ) ) ?
148+ }
149+
150+ fn deliver ( socket : & UnixDatagram , bytes : & [ u8 ] ) -> anyhow:: Result < ( ) > {
151+ let deadline = Instant :: now ( ) + SEND_RETRY_BUDGET ;
152+ loop {
153+ match socket. send ( bytes) {
154+ Ok ( _) => return Ok ( ( ) ) ,
155+ Err ( e) if is_transient ( & e) && Instant :: now ( ) < deadline => sleep ( SEND_RETRY_BACKOFF ) ,
156+ Err ( e) => return Err ( e) . context ( "deliver line to socket" ) ,
157+ }
158+ }
159+ }
160+
161+ fn is_transient ( error : & std:: io:: Error ) -> bool {
162+ const ENOBUFS : i32 = 105 ;
163+ matches ! ( error. kind( ) , ErrorKind :: WouldBlock | ErrorKind :: Interrupted ) || error. raw_os_error ( ) == Some ( ENOBUFS )
141164}
142165
143166/// Wait for the remote process to bind `path`, intentionally naive. Returns
0 commit comments