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 ;
89use std:: os:: unix:: net:: UnixDatagram ;
910use std:: path:: Path ;
1011use std:: sync:: mpsc:: sync_channel;
1112use std:: thread:: { self , sleep} ;
1213use std:: time:: { Duration , Instant } ;
1314
1415use antithesis_sdk:: random:: { random_choice, AntithesisRng } ;
16+ use anyhow:: Context ;
1517use rand:: { rand_core:: UnwrapErr , RngExt } ;
1618
1719use 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
0 commit comments