//! The edge between two circuits: a producer and a consumer joined by stamped, coalescing //! deltas equal one circuit running both. Evidence: `context/test-evidence/zset.md`. mod support; use proptest::prelude::*; use std::sync::Arc; use std::sync::atomic::{AtomicUsize, Ordering}; use std::thread; use support::Text; use zset::{ ChannelPipe, Circuit, Correction, Delta, Direct, EdgeCodec, EdgeSink, EdgeSource, Flow, Frame, Input, Output, Pipe, Recv, Refused, Stamp, ZSet, channel_pipes, }; type Z = ZSet; type Counts = ZSet<(u8, i64)>; fn z(entries: &[(u8, i64)]) -> Z { entries.iter().copied().collect() } fn mapped(d: &Z) -> Z { d.map(|k| k % 7) } /// The producer's graph: one map. fn producer() -> (Circuit, Input, Output) { let mut c = Circuit::new(); let (i, s) = c.input::(); let m = c.map(&s, |k| k % 7); let o = c.output(&m); (c, i, o) } /// The consumer's graph: distinct, then a count per group. fn consumer_tail(c: &mut Circuit, s: &zset::Stream) -> Output<(u8, i64)> { let d = c.distinct(s); let g = c.index_by(&d, |k| k % 3); let a = c.aggregate(&g, |_, vals| Some(vals.len() as i64)); c.output(&a) } fn consumer() -> (Circuit, Input, Output<(u8, i64)>) { let mut c = Circuit::new(); let (i, s) = c.input::(); let o = consumer_tail(&mut c, &s); (c, i, o) } /// Both graphs in one circuit: what the edge must be equal to. fn single() -> (Circuit, Input, Output<(u8, i64)>) { let mut c = Circuit::new(); let (i, s) = c.input::(); let m = c.map(&s, |k| k % 7); let o = consumer_tail(&mut c, &m); (c, i, o) } /// A pipe end that can lose what is sent through it. struct FaultPipe { inner: ChannelPipe, lose: Arc, } impl Pipe for FaultPipe { fn try_send(&mut self, wire: W) -> Result<(), Refused> { if self.lose.load(Ordering::SeqCst) > 0 { self.lose.fetch_sub(1, Ordering::SeqCst); return Ok(()); } self.inner.try_send(wire) } fn try_recv(&mut self) -> Result { self.inner.try_recv() } } fn fault( cap: usize, ) -> ( FaultPipe, FaultPipe, Arc, Arc, ) { let (a, b) = channel_pipes(cap); let (la, lb) = (Arc::new(AtomicUsize::new(0)), Arc::new(AtomicUsize::new(0))); ( FaultPipe { inner: a, lose: la.clone(), }, FaultPipe { inner: b, lose: lb.clone(), }, la, lb, ) } type Sink = EdgeSink>::Wire>>; type Source = EdgeSource>::Wire>>; /// Two circuits joined by an edge, and the combined circuit they must equal. struct World + Clone> { codec: C, prod: Circuit, p_in: Input, p_out: Output, sink: Sink, lose_fwd: Arc, cons: Circuit, c_out: Output<(u8, i64)>, src: Source, lose_back: Arc, got: Counts, p_total: Z, epoch: u64, one: Circuit, one_in: Input, one_out: Output<(u8, i64)>, want: Counts, } impl + Clone> World { fn new(codec: C, cap: usize) -> Self { let (prod, p_in, p_out) = producer(); let (cons, c_in, c_out) = consumer(); let (one, one_in, one_out) = single(); let (a, b, lose_fwd, lose_back) = fault(cap); World { sink: EdgeSink::new(codec.clone(), a, 1), src: EdgeSource::new(codec.clone(), b, c_in), codec, prod, p_in, p_out, lose_fwd, cons, c_out, lose_back, got: Counts::new(), p_total: Z::new(), epoch: 1, one, one_in, one_out, want: Counts::new(), } } fn commit(&mut self, d: &Z) { self.prod.begin(); self.prod.push(&self.p_in, d.clone()); self.prod.step(); self.prod.commit(); self.sink.committed_from(&mut self.prod, &self.p_out); self.p_total += &mapped(d); self.one.push(&self.one_in, d.clone()); self.one.step(); self.want += self.one.take(&self.one_out); } fn abort(&mut self, d: &Z) { let before = self.sink.stamp(); self.prod.begin(); self.prod.push(&self.p_in, d.clone()); self.prod.step(); self.prod.rollback(); assert_eq!( self.sink.stamp(), before, "[abort] a rolled-back command was stamped" ); assert_eq!( self.prod.take(&self.p_out), Z::new(), "[abort] its output survived" ); } fn step_consumer(&mut self) { self.cons.step(); self.got += self.cons.take(&self.c_out); } fn restart_sink(&mut self) { self.epoch += 1; let old = std::mem::replace( &mut self.sink, EdgeSink::new(self.codec.clone(), fault_placeholder(), 0), ); self.sink = old.restart(self.epoch, 0, self.p_total.clone()); } fn restart_source(&mut self) { let (cons, c_in, c_out) = consumer(); let old = std::mem::replace( &mut self.src, EdgeSource::new(self.codec.clone(), fault_placeholder(), c_in), ); self.src = old.restart(c_in); self.cons = cons; self.c_out = c_out; self.got = Counts::new(); } /// Runs the edge until it is idle, with nothing lost any more. fn settle(&mut self) { self.lose_fwd.store(0, Ordering::SeqCst); self.lose_back.store(0, Ordering::SeqCst); for _ in 0..200 { self.sink.pump(); let read = self.src.poll(&mut self.cons, 8); self.step_consumer(); if !self.sink.backlog() && !self.src.stale() && self.src.stamp() == Some(self.sink.stamp()) { break; } if read == 0 { // Nothing arrived: the host's timeout fires and the receiver asks again. self.src.connection_lost(); } } assert_eq!( self.src.stamp(), Some(self.sink.stamp()), "[settle] never caught up" ); self.step_consumer(); } } /// A pipe end that is never used: only to fill a slot while the real one is moved. fn fault_placeholder() -> FaultPipe { fault(1).0 } #[derive(Clone, Debug)] enum Act { Commit(Vec<(u8, i64)>), Abort(Vec<(u8, i64)>), Pump, Poll(usize), StepConsumer, LoseForward(usize), LoseBack(usize), RestartSink, RestartSource, ConnectionLost, } fn delta() -> impl Strategy> { prop::collection::vec((0u8..14, -2i64..=2), 0..4) } fn act() -> impl Strategy { prop_oneof![ 6 => delta().prop_map(Act::Commit), 1 => delta().prop_map(Act::Abort), 4 => Just(Act::Pump), 4 => (1usize..4).prop_map(Act::Poll), 3 => Just(Act::StepConsumer), 1 => (1usize..3).prop_map(Act::LoseForward), 1 => (1usize..3).prop_map(Act::LoseBack), 1 => Just(Act::RestartSink), 1 => Just(Act::RestartSource), 1 => Just(Act::ConnectionLost), ] } fn run + Clone>(codec: C, cap: usize, acts: &[Act]) { let mut w = World::new(codec, cap); for a in acts { match a { Act::Commit(d) => w.commit(&d.iter().copied().collect()), Act::Abort(d) => w.abort(&d.iter().copied().collect()), Act::Pump => { w.sink.pump(); } Act::Poll(n) => { w.src.poll(&mut w.cons, *n); } Act::StepConsumer => w.step_consumer(), Act::LoseForward(n) => w.lose_fwd.store(*n, Ordering::SeqCst), Act::LoseBack(n) => w.lose_back.store(*n, Ordering::SeqCst), Act::RestartSink => w.restart_sink(), Act::RestartSource => w.restart_source(), Act::ConnectionLost => w.src.connection_lost(), } } w.settle(); assert_eq!( w.got, w.want, "[edge] the consumer's output after the edge settles" ); } proptest! { /// The differential: any history of commits, aborts, pumps, polls, losses and restarts, /// once settled, leaves the consumer where one combined circuit would be. #[test] fn two_circuits_on_an_edge_equal_one_circuit( acts in prop::collection::vec(act(), 0..40), cap in 1usize..4, ) { run(Direct, cap, &acts); } /// The same histories through a text wire form. #[test] fn the_same_histories_through_a_text_codec( acts in prop::collection::vec(act(), 0..40), cap in 1usize..4, ) { run(Text, cap, &acts); } } /// Runs the edge until quiet, stepping the consumer between polls. fn drive>( sink: &mut EdgeSink>, src: &mut EdgeSource>, c: &mut Circuit, ) { for _ in 0..20 { sink.pump(); src.poll(c, 100); c.step(); } } /// A consumer circuit that only mirrors its input, and a sink and source on a fault pipe. fn mirror( cap: usize, ) -> ( Sink, Source, Circuit, Output, Arc, ) { let (a, b, lose_fwd, _) = fault(cap); let mut c = Circuit::new(); let (i, s) = c.input::(); let o = c.output(&s); ( EdgeSink::new(Direct, a, 1), EdgeSource::new(Direct, b, i), c, o, lose_fwd, ) } #[test] fn deltas_arrive_in_order_and_are_applied() { let (mut sink, mut src, mut c, out, _) = mirror(8); sink.committed(z(&[(1, 1)])); sink.pump(); sink.committed(z(&[(2, 1)])); sink.pump(); drive(&mut sink, &mut src, &mut c); assert_eq!(c.take(&out), z(&[(1, 1), (2, 1)])); assert_eq!(src.stamp(), Some(Stamp { epoch: 1, seq: 2 })); } #[test] fn a_full_pipe_coalesces_exactly_and_keeps_the_range() { let (a, mut b) = channel_pipes::>(1); let mut sink = EdgeSink::new(Direct, a, 1); sink.committed(z(&[(1, 1)])); assert_eq!(sink.pump(), Flow::Idle); for k in 2..6 { sink.committed(z(&[(k, 1), (1, -1)])); assert_eq!(sink.pump(), Flow::Backlogged, "[full] command {k}"); } assert!(matches!(b.try_recv(), Ok(Frame::Delta(d)) if (d.from, d.to) == (0, 1))); assert_eq!(sink.pump(), Flow::Idle); let Ok(Frame::Delta(d)) = b.try_recv() else { panic!("a merged delta") }; assert_eq!( (d.from, d.to), (1, 5), "[range] the merged delta covers (1, 5]" ); assert_eq!( d.delta, z(&[(2, 1), (3, 1), (4, 1), (5, 1), (1, -4)]), "[sum] coalescing sums exactly" ); } #[test] fn an_empty_command_is_still_a_stamp() { let (mut sink, mut src, mut c, _, _) = mirror(8); sink.committed(Z::new()); sink.committed(Z::new()); drive(&mut sink, &mut src, &mut c); assert_eq!(src.stamp(), Some(Stamp { epoch: 1, seq: 2 })); } #[test] fn a_lost_delta_is_seen_in_the_stamps_and_corrected() { let (mut sink, mut src, mut c, out, lose) = mirror(8); drive(&mut sink, &mut src, &mut c); sink.committed(z(&[(1, 1)])); sink.pump(); src.poll(&mut c, 10); lose.store(1, Ordering::SeqCst); sink.committed(z(&[(2, 1)])); sink.pump(); sink.committed(z(&[(3, 1)])); sink.pump(); src.poll(&mut c, 10); assert!( src.stale(), "[gap] the third delta does not follow the first" ); drive(&mut sink, &mut src, &mut c); assert_eq!(c.take(&out), z(&[(1, 1), (2, 1), (3, 1)]), "[correction]"); assert!(!src.stale()); } #[test] fn a_duplicate_delta_is_applied_once() { let (a, mut b) = channel_pipes::>(8); let mut c = Circuit::new(); let (i, s) = c.input::(); let o = c.output(&s); let mut src = EdgeSource::new(Direct, a, i); src.poll(&mut c, 10); let join = answer(&mut b, 1, 0, Z::new()); let d = Frame::Delta(Delta { epoch: 1, from: 0, to: 1, delta: z(&[(1, 1)]), }); b.try_send(join).unwrap(); b.try_send(d.clone()).unwrap(); b.try_send(d).unwrap(); src.poll(&mut c, 10); assert!(!src.stale(), "[dup] a duplicate is not a gap"); c.step(); assert_eq!(c.take(&o), z(&[(1, 1)])); } #[test] fn a_restarted_sender_is_corrected_not_trusted() { let (mut sink, mut src, mut c, out, _) = mirror(8); sink.committed(z(&[(1, 1)])); sink.committed(z(&[(2, 1)])); drive(&mut sink, &mut src, &mut c); let _ = c.take(&out); // A new incarnation that has since committed one command on top of {1, 2, 9}. let mut sink = sink.restart(2, 0, z(&[(1, 1), (2, 1)])); sink.committed(z(&[(9, 1)])); drive(&mut sink, &mut src, &mut c); assert_eq!( c.take(&out), z(&[(9, 1)]), "[restart] only the difference is applied" ); assert_eq!(src.stamp(), Some(Stamp { epoch: 2, seq: 1 })); } /// Answers the receiver's latest request in `b` with a correction that echoes its token. fn answer(b: &mut ChannelPipe>, epoch: u64, through: u64, target: Z) -> Frame { let mut token = None; while let Ok(f) = b.try_recv() { if let Frame::Request(r) = f { token = Some(r.token); } } Frame::Correction(Correction { epoch, through, token: token.expect("a request was waiting"), target, }) } fn reply(b: &mut ChannelPipe>, epoch: u64, through: u64, target: Z) { let f = answer(b, epoch, through, target); b.try_send(f).unwrap(); } #[test] fn a_late_reply_from_a_dead_incarnation_changes_nothing() { let (a, mut b) = channel_pipes::>(8); let mut c = Circuit::new(); let (i, s) = c.input::(); let o = c.output(&s); let mut src = EdgeSource::new(Direct, a, i); src.poll(&mut c, 10); // Epoch 100 reads the first request; its reply is delayed. The sender restarts as 1001. let late = answer(&mut b, 100, 1, z(&[(9, 1)])); src.connection_lost(); src.poll(&mut c, 10); reply(&mut b, 1001, 1, z(&[(5, 1)])); src.poll(&mut c, 10); assert_eq!( src.stamp(), Some(Stamp { epoch: 1001, seq: 1 }) ); b.try_send(late).unwrap(); b.try_send(Frame::Delta(Delta { epoch: 1001, from: 1, to: 2, delta: z(&[(6, 1)]), })) .unwrap(); src.poll(&mut c, 10); c.step(); assert_eq!( src.stamp(), Some(Stamp { epoch: 1001, seq: 2 }), "[late] the reply of an incarnation never joined does not move the receiver back" ); assert_eq!( c.take(&o), z(&[(5, 1), (6, 1)]), "[late] and 1001 is not shut out" ); } #[test] fn an_unsolicited_correction_from_another_incarnation_only_marks_stale() { let (a, mut b) = channel_pipes::>(8); let mut c = Circuit::new(); let (i, s) = c.input::(); let _o = c.output(&s); let mut src = EdgeSource::new(Direct, a, i); src.poll(&mut c, 10); reply(&mut b, 1, 1, Z::new()); src.poll(&mut c, 10); assert!(!src.stale()); let wrong = Frame::Correction(Correction { epoch: 2, through: 9, token: 77, target: z(&[(3, 1)]), }); b.try_send(wrong).unwrap(); src.poll(&mut c, 10); assert_eq!( src.stamp(), Some(Stamp { epoch: 1, seq: 1 }), "[foreign] not moved" ); assert!(src.stale(), "[foreign] but it asks"); } #[test] fn only_the_reply_to_the_latest_request_may_join_a_new_incarnation() { let (a, mut b) = channel_pipes::>(8); let mut c = Circuit::new(); let (i, s) = c.input::(); let _o = c.output(&s); let mut src = EdgeSource::new(Direct, a, i); src.poll(&mut c, 10); let late = answer(&mut b, 100, 1, z(&[(9, 1)])); src.connection_lost(); src.poll(&mut c, 10); b.try_send(late).unwrap(); src.poll(&mut c, 10); assert_eq!( src.stamp(), None, "[token] the reply to the first request is not the answer" ); reply(&mut b, 1001, 1, z(&[(5, 1)])); src.poll(&mut c, 10); assert_eq!( src.stamp(), Some(Stamp { epoch: 1001, seq: 1 }) ); } #[test] fn an_answer_older_than_the_watermark_does_not_clear_stale() { let (a, mut b) = channel_pipes::>(8); let mut c = Circuit::new(); let (i, s) = c.input::(); let _o = c.output(&s); let mut src = EdgeSource::new(Direct, a, i); src.poll(&mut c, 10); reply(&mut b, 1, 3, Z::new()); src.poll(&mut c, 10); src.connection_lost(); src.poll(&mut c, 10); reply(&mut b, 1, 1, Z::new()); src.poll(&mut c, 10); assert_eq!( src.stamp(), Some(Stamp { epoch: 1, seq: 3 }), "[older] never moves back" ); assert!( src.stale(), "[older] and it does not say the receiver is current" ); } #[test] fn traffic_never_goes_down_across_a_restart_of_either_end() { let (mut sink, mut src, mut c, _, _) = mirror(8); sink.committed(z(&[(1, 1)])); drive(&mut sink, &mut src, &mut c); let (s, r) = (sink.traffic(), src.traffic()); assert!(s > 0 && r > 0, "[setup] frames moved both ways"); let sink = sink.restart(2, 0, Z::new()); let mut c2 = Circuit::new(); let (i2, _) = c2.input::(); let src = src.restart(i2); assert_eq!( (sink.traffic(), src.traffic()), (s, r), "[restart] counters kept" ); } #[test] fn a_new_epoch_frame_re_asks_once_after_a_lost_request() { let (a, mut b) = channel_pipes::>(64); let mut c = Circuit::new(); let (i, s) = c.input::(); let _o = c.output(&s); let mut src = EdgeSource::new(Direct, a, i); src.poll(&mut c, 8); assert!( matches!(b.try_recv(), Ok(Frame::Request(_))), "[first] the request" ); // The sender read it and restarted before answering. let mut asks = 0; for n in 0..5u64 { b.try_send(Frame::Delta(Delta { epoch: 2, from: n, to: n + 1, delta: Z::new(), })) .unwrap(); src.poll(&mut c, 8); while let Ok(Frame::Request(_)) = b.try_recv() { asks += 1; } } assert_eq!( asks, 1, "[reask] once for the new epoch, not once per frame" ); } #[test] fn an_answer_at_the_watermark_says_nothing_newer_and_clears_stale() { let (a, mut b) = channel_pipes::>(8); let mut c = Circuit::new(); let (i, s) = c.input::(); let _o = c.output(&s); let mut src = EdgeSource::new(Direct, a, i); src.poll(&mut c, 8); reply(&mut b, 1, 1, Z::new()); src.poll(&mut c, 8); src.connection_lost(); src.poll(&mut c, 8); reply(&mut b, 1, 1, Z::new()); src.poll(&mut c, 8); assert!( !src.stale(), "[dup-answer] through == watermark clears stale" ); } #[test] fn trouble_seen_after_a_request_keeps_its_answer_from_clearing_stale() { let (a, mut b) = channel_pipes::>(8); let mut c = Circuit::new(); let (i, s) = c.input::(); let _o = c.output(&s); let mut src = EdgeSource::new(Direct, a, i); src.poll(&mut c, 8); reply(&mut b, 1, 1, Z::new()); src.poll(&mut c, 8); src.connection_lost(); src.poll(&mut c, 8); let reply = answer(&mut b, 1, 1, Z::new()); // A gap seen after the request went out: the reply was cut before it. b.try_send(Frame::Delta(Delta { epoch: 1, from: 4, to: 5, delta: Z::new(), })) .unwrap(); b.try_send(reply).unwrap(); src.poll(&mut c, 8); assert!(src.stale(), "[marks] the reply predates the gap"); } #[test] fn a_poll_inside_a_transaction_is_refused() { let (a, mut b) = channel_pipes::>(8); let mut c = Circuit::new(); let (i, s) = c.input::(); let o = c.output(&s); let mut src = EdgeSource::new(Direct, a, i); src.poll(&mut c, 8); reply(&mut b, 1, 1, z(&[(1, 1)])); // Outside a transaction a rollback of the consumer's step keeps what was polled. src.poll(&mut c, 8); c.begin(); c.step(); c.rollback(); c.step(); assert_eq!( c.take(&o), z(&[(1, 1)]), "[txn] a step rolled back after poll loses nothing" ); c.begin(); let r = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| src.poll(&mut c, 8))); assert!( r.is_err(), "[txn] a poll inside a transaction would be dropped by rollback" ); } #[test] #[should_panic(expected = "at least one item")] fn a_zero_capacity_pipe_is_refused() { let _ = channel_pipes::(0); } #[test] fn a_committed_that_overflows_leaves_the_sink_as_it_was() { let (mut sink, mut src, mut c, _, _) = mirror(8); sink.committed(z(&[(1, i64::MAX)])); let r = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| { sink.committed(z(&[(2, 1), (1, 1)])) })); assert!(r.is_err(), "the weight overflows"); assert_eq!( sink.stamp(), Stamp { epoch: 1, seq: 1 }, "[overflow] no stamp consumed" ); drive(&mut sink, &mut src, &mut c); assert_eq!( src.held(), &z(&[(1, i64::MAX)]), "[overflow] and the key 2 did not slip in" ); } #[test] fn an_unreadable_frame_asks_for_a_correction() { let (a, mut b) = channel_pipes::(8); let mut c = Circuit::new(); let (i, s) = c.input::(); let o = c.output(&s); let mut src = EdgeSource::new(Text, a, i); b.try_send("D 1 0 1 1:1".to_string()).unwrap(); src.poll(&mut c, 10); assert!( src.stale(), "[initial] a new receiver asks for a correction" ); let request = b.try_recv().unwrap(); let token = request.split(' ').nth(1).unwrap(); b.try_send(format!("C 1 1 {token} 1:1")).unwrap(); src.poll(&mut c, 10); assert!(!src.stale()); b.try_send("garbage".to_string()).unwrap(); src.poll(&mut c, 10); assert!(src.stale(), "[unreadable] a frame that does not decode"); c.step(); assert_eq!(c.take(&o), z(&[(1, 1)])); } #[test] fn a_rolled_back_command_does_not_cross() { let (mut sink, mut src, mut c, out, _) = mirror(8); let (mut p, p_in, p_out) = producer(); // The host's command fails and rolls back; the sink is not asked. p.begin(); p.push(&p_in, z(&[(1, 1)])); p.step(); p.rollback(); // The retry is a different command and commits. p.begin(); p.push(&p_in, z(&[(2, 1)])); p.step(); p.commit(); assert_eq!( sink.committed_from(&mut p, &p_out), Stamp { epoch: 1, seq: 1 } ); drive(&mut sink, &mut src, &mut c); assert_eq!( c.take(&out), z(&[(2, 1)]), "[retry] only the committed command crossed" ); } #[test] #[should_panic(expected = "only after it commits")] fn taking_a_delta_inside_a_transaction_is_refused() { let (mut sink, ..) = mirror(8); let (mut p, p_in, p_out) = producer(); p.begin(); p.push(&p_in, z(&[(1, 1)])); p.step(); sink.committed_from(&mut p, &p_out); } #[test] fn a_circuit_on_each_of_two_threads_equals_one_circuit() { const N: u64 = 300; let deltas: Vec = (0..N) .map(|n| z(&[((n * 7 % 14) as u8, 1 - (n % 3) as i64), ((n % 5) as u8, 1)])) .collect(); let (mut one, one_in, one_out) = single(); let mut want = Counts::new(); for d in &deltas { one.push(&one_in, d.clone()); one.step(); want += one.take(&one_out); } let (a, b) = channel_pipes::>(2); let finished = Arc::new(AtomicUsize::new(0)); let fin = finished.clone(); let producer_thread = thread::spawn(move || { let (mut p, p_in, p_out) = producer(); let mut sink = EdgeSink::new(Direct, a, 9); for (n, d) in deltas.iter().enumerate() { p.begin(); p.push(&p_in, d.clone()); p.step(); p.commit(); sink.committed_from(&mut p, &p_out); if n % 3 != 0 { sink.pump(); } if n % 7 == 0 { thread::yield_now(); } } while fin.load(Ordering::SeqCst) == 0 { sink.pump(); thread::yield_now(); } }); let (mut c, c_in, c_out) = consumer(); let mut src = EdgeSource::new(Direct, b, c_in); let mut got = Counts::new(); let mut spins = 0u64; while src.stamp() != Some(Stamp { epoch: 9, seq: N }) { spins += 1; assert!(spins < 50_000_000, "[threads] the consumer never caught up"); src.poll(&mut c, 3); c.step(); got += c.take(&c_out); if spins.is_multiple_of(5) { thread::yield_now(); } } c.step(); got += c.take(&c_out); finished.store(1, Ordering::SeqCst); producer_thread.join().unwrap(); assert_eq!( got, want, "[threads] the consumer after the producer thread finishes" ); } #[test] fn a_poll_reads_at_most_the_frames_it_is_given() { let (a, mut b) = channel_pipes::>(8); let mut c = Circuit::new(); let (i, _) = c.input::(); let mut src = EdgeSource::new(Direct, a, i); src.poll(&mut c, 0); let Ok(Frame::Request(request)) = b.try_recv() else { panic!("a request") }; for through in 0..3 { let target = Z::new(); let frame = Frame::Correction(Correction { epoch: 1, through, token: request.token, target, }); b.try_send(frame).unwrap(); } assert_eq!(src.poll(&mut c, 2), 2, "[budget] two of three"); assert_eq!(src.stamp(), Some(Stamp { epoch: 1, seq: 1 })); assert_eq!(src.poll(&mut c, 2), 1, "[budget] the rest"); assert_eq!(src.stamp(), Some(Stamp { epoch: 1, seq: 2 })); }