Skip to main content

darkbio_wire/transport/
stream.rs

1// wire-rs: encrypted protocol between Ark and host
2// Copyright 2026 Dark Bio AG. All rights reserved.
3
4//! The byte stream and its shutdown operation, owned together by the transport.
5
6use super::io::check_deadline;
7use super::{DEFAULT_WRITE_TIMEOUT, Read, Write};
8use std::fmt;
9use std::io;
10use std::sync::{Arc, Condvar, Mutex};
11use std::time::{Duration, Instant};
12use tracing::debug;
13
14/// A duplex byte stream with a shutdown operation for both directions.
15///
16/// Shutdown must release blocked reads, writes and flushes, including reads
17/// with no deadline. Socket adapters can shut down the underlying socket.
18/// The shutdown operation must return promptly, must not panic, and must not
19/// acquire a lock held by a blocked I/O operation. It runs at most once.
20/// Neither shutdown nor an I/O operation may call this stream's closer. Closing
21/// would wait for the calling operation itself to return.
22///
23/// Closing refuses further adapter I/O. Admitted operations return their normal
24/// results and may succeed while shutdown is in progress. A failed frame send
25/// may have moved any prefix of its bytes. A successful write and flush means
26/// the adapter took the bytes, not that the peer received or processed them.
27/// Data already buffered by the transport may still be received after closing.
28///
29/// Dropping the stream closes it. Passing it to a client or server transfers
30/// that responsibility to the transport owner. Closer handles do not keep the
31/// reader or writer alive, and dropping a handle does not close the stream.
32pub struct Stream<R: Read, W: Write> {
33    io: Option<(R, W)>, // Taken when ownership passes to the transport
34    closer: Closer,
35    timeout: Duration, // One budget for the frame's writes and flush
36}
37
38impl<R: Read, W: Write> Stream<R, W> {
39    /// Bundles the two I/O directions with their shutdown operation.
40    pub fn new(reader: R, writer: W, shutdown: impl FnOnce() + Send + 'static) -> Self {
41        Self {
42            io: Some((reader, writer)),
43            closer: Closer::new(shutdown),
44            timeout: DEFAULT_WRITE_TIMEOUT,
45        }
46    }
47
48    /// Sets the budget for writing and flushing one complete frame, including
49    /// any delimiter needed after failed output. Progress does not restart it.
50    /// The budget begins after acquiring the writer and includes frame encoding.
51    /// Waiting for locks, encryption and peer replies is outside this budget.
52    /// Handshake frames also share the overall handshake deadline, which can
53    /// shorten this write budget.
54    ///
55    /// Zero refuses output immediately. A duration too large to add to an
56    /// [`Instant`] panics when an outgoing frame's deadline is constructed.
57    pub fn set_write_timeout(mut self, timeout: Duration) -> Self {
58        self.timeout = timeout;
59        self
60    }
61
62    /// A handle that can close the stream from another thread.
63    pub fn closer(&self) -> Closer {
64        self.closer.clone()
65    }
66
67    /// Permanently closes the stream and waits for shutdown and admitted adapter
68    /// operations to finish. Concurrent close calls wait for the same completion.
69    pub fn close(&self) {
70        self.closer.close();
71    }
72
73    /// Transfers ownership to a transport without closing the stream.
74    pub(crate) fn into_parts(mut self) -> (R, W, Closer, Duration) {
75        let (reader, writer) = self.io.take().expect("stream consumed once");
76        (reader, writer, self.closer.clone(), self.timeout)
77    }
78}
79
80impl<R: Read, W: Write> Drop for Stream<R, W> {
81    fn drop(&mut self) {
82        if self.io.is_some() {
83            self.close();
84        }
85    }
86}
87
88impl<R: Read, W: Write> fmt::Debug for Stream<R, W> {
89    /// Shows the write budget and the shutdown state, never the adapters.
90    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
91        f.debug_struct("Stream")
92            .field("timeout", &self.timeout)
93            .field("closer", &self.closer)
94            .finish_non_exhaustive()
95    }
96}
97
98/// A cloneable handle that permanently closes a byte stream.
99#[derive(Clone)]
100pub struct Closer(Arc<Shutdown>);
101
102/// Shared shutdown coordination. The state lock orders I/O admission and closure.
103/// Adapter operations and the shutdown callback run without this lock. The
104/// condition variable wakes waiting closers as those operations finish.
105struct Shutdown {
106    state: Mutex<State>,
107    changed: Condvar,
108}
109
110/// Lifecycle of a byte stream. Closing refuses new adapter operations. Closed
111/// additionally guarantees that shutdown and all admitted operations have finished.
112#[derive(Clone, Copy, Debug, PartialEq, Eq)]
113enum Phase {
114    /// Adapter I/O may be admitted and shutdown has not been requested.
115    Open,
116    /// One closer runs shutdown. Other closers wait and new I/O is refused.
117    Closing,
118    /// The callback and every admitted adapter call have returned.
119    Closed,
120}
121
122/// Stream lifecycle and active adapter operations under one lock. The first
123/// closer takes the shutdown action. Each admitted operation increments the
124/// active count and decrements it on completion. Closed requires a zero count.
125struct State {
126    phase: Phase,
127    active: usize, // Admitted adapter operations that shutdown must wait for
128    action: Option<Box<dyn FnOnce() + Send>>,
129}
130
131impl Closer {
132    /// Creates the shutdown coordinator before either I/O half can be used.
133    pub(super) fn new(shutdown: impl FnOnce() + Send + 'static) -> Self {
134        Self(Arc::new(Shutdown {
135            state: Mutex::new(State {
136                phase: Phase::Open,
137                active: 0,
138                action: Some(Box::new(shutdown)),
139            }),
140            changed: Condvar::new(),
141        }))
142    }
143
144    /// Permanently closes the stream. Every caller waits until the shutdown
145    /// callback and all admitted adapter calls have returned. This includes
146    /// deadline setters, reads, writes and flushes.
147    /// New I/O is refused as soon as closing begins. This does not join the
148    /// threads using the transport or wait for application handlers.
149    ///
150    /// The callback runs once, without holding a state or I/O lock. It must return
151    /// promptly and release blocked I/O, including reads with no deadline.
152    /// Calling close from adapter I/O or the callback would wait on itself.
153    pub fn close(&self) {
154        // Wait for another closer to finish or take responsibility for shutdown
155        let action = {
156            let mut state = self.0.state.lock().expect("stream state not poisoned");
157            loop {
158                match state.phase {
159                    // Stream already closed, return early
160                    Phase::Closed => return,
161
162                    // Another closer is running shutdown. Wait for it to finish.
163                    Phase::Closing => {
164                        state = self
165                            .0
166                            .changed
167                            .wait(state)
168                            .expect("stream state not poisoned");
169                    }
170
171                    // Stream open, mark it closing and begin teardown
172                    Phase::Open => {
173                        debug!("closing wire stream");
174                        state.phase = Phase::Closing;
175                        break state.action.take().expect("shutdown called once");
176                    }
177                }
178            }
179        };
180
181        // The first closer runs shutdown without holding the state lock.
182        action();
183
184        // Wait until all admitted adapter operations return
185        let mut state = self.0.state.lock().expect("stream state not poisoned");
186        while state.active != 0 {
187            state = self
188                .0
189                .changed
190                .wait(state)
191                .expect("stream state not poisoned");
192        }
193
194        // Mark the stream closed and wake any threads blocked on close
195        state.phase = Phase::Closed;
196        self.0.changed.notify_all();
197    }
198
199    /// Admits one adapter call atomically with the decision to start closing.
200    fn enter(&self) -> Option<Activity<'_>> {
201        let mut state = self.0.state.lock().expect("stream state not poisoned");
202        if state.phase != Phase::Open {
203            return None;
204        }
205        state.active += 1;
206        Some(Activity(self))
207    }
208}
209
210impl fmt::Debug for Closer {
211    /// Shows the lifecycle phase and the admitted adapter operations. A state
212    /// lock held elsewhere is reported instead of waited for.
213    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
214        let mut closer = f.debug_struct("Closer");
215        match self.0.state.try_lock() {
216            Ok(state) => closer
217                .field("phase", &state.phase)
218                .field("active", &state.active),
219            Err(_) => closer.field("state", &format_args!("<locked>")),
220        };
221        closer.finish()
222    }
223}
224
225/// Tracks one admitted adapter operation until return or unwind. Dropping it
226/// decrements the active count without acquiring an I/O lock.
227struct Activity<'a>(&'a Closer);
228
229impl Drop for Activity<'_> {
230    fn drop(&mut self) {
231        let mut state = self.0.0.state.lock().expect("stream state not poisoned");
232        state.active -= 1;
233        if state.active == 0 {
234            self.0.0.changed.notify_all();
235        }
236    }
237}
238
239/// Reader admitting each adapter call under the shutdown coordinator's lock.
240pub(super) struct ReadHalf<R> {
241    pub(super) inner: R,
242    pub(super) closer: Closer,
243}
244
245impl<R: Read> ReadHalf<R> {
246    /// Reads with the optional handshake deadline. Ordinary reads wait for data
247    /// or adapter shutdown. Timeouts and interruptions retry within the deadline.
248    /// A successful read reports its bytes even if it finishes late, so the framer
249    /// can retain them before surfacing expiry. Closure refuses new I/O with EOF.
250    pub(super) fn read(&mut self, buf: &mut [u8], deadline: Option<Instant>) -> io::Result<usize> {
251        loop {
252            // Refuse an operation whose deadline has expired
253            if let Some(deadline) = deadline {
254                check_deadline(deadline)?;
255            }
256            // Keep the setter and read accounted for until both have returned.
257            let result = {
258                let Some(_active) = self.closer.enter() else {
259                    return Ok(0);
260                };
261                self.inner.set_read_deadline(deadline)?;
262                self.inner.read(buf)
263            };
264            // Retry an idle timeout or interrupted read. Other errors return.
265            match result {
266                Err(err)
267                    if matches!(
268                        err.kind(),
269                        io::ErrorKind::TimedOut | io::ErrorKind::Interrupted
270                    ) =>
271                {
272                    continue;
273                }
274                result => return result,
275            }
276        }
277    }
278}
279
280/// Writer admitting each partial write and flush under the shutdown lock.
281pub(super) struct WriteHalf<W> {
282    pub(super) inner: W,
283    pub(super) closer: Closer,
284}
285
286impl<W: Write> WriteHalf<W> {
287    /// Writes all bytes and flushes them under one absolute deadline. Installs
288    /// the deadline once before I/O. Each partial write and flush checks
289    /// expiration and closure before calling the adapter.
290    /// Interrupted writes are retried. Zero progress fails with `WriteZero`.
291    /// Setter and flush errors are not retried.
292    ///
293    /// An admitted call may finish after closure begins. Failure
294    /// can leave a written prefix. The framer checks the deadline again after
295    /// this operation returns, so a late flush fails the complete frame.
296    pub(super) fn write(&mut self, mut bytes: &[u8], deadline: Instant) -> io::Result<()> {
297        // Refuse an operation whose deadline has expired
298        check_deadline(deadline)?;
299
300        // Account for the deadline setter so shutdown waits for it too.
301        {
302            let _active = self
303                .closer
304                .enter()
305                .ok_or_else(|| io::Error::new(io::ErrorKind::NotConnected, "stream closed"))?;
306            self.inner.set_write_deadline(deadline)?;
307        }
308        // Keep writing while bytes remain; flush only after the complete write
309        while !bytes.is_empty() {
310            // Recheck the deadline before each partial write.
311            check_deadline(deadline)?;
312
313            // Attempt to write as much data as possible
314            let result = {
315                let _active = self
316                    .closer
317                    .enter()
318                    .ok_or_else(|| io::Error::new(io::ErrorKind::NotConnected, "stream closed"))?;
319                self.inner.write(bytes)
320            };
321            match result {
322                Ok(0) => return Err(io::ErrorKind::WriteZero.into()),
323                Ok(n) => bytes = &bytes[n..],
324                Err(err) if err.kind() == io::ErrorKind::Interrupted => continue,
325                Err(err) => return Err(err),
326            }
327        }
328        // Flush is part of the same operation and gets its own admission
329        check_deadline(deadline)?;
330
331        let _active = self
332            .closer
333            .enter()
334            .ok_or_else(|| io::Error::new(io::ErrorKind::NotConnected, "stream closed"))?;
335        self.inner.flush()
336    }
337}
338
339#[cfg(test)]
340#[cfg_attr(coverage_nightly, coverage(off))]
341mod tests {
342    use super::*;
343    use crate::transport::Client;
344    use crate::transport::testing::Memory;
345    use std::sync::atomic::{AtomicUsize, Ordering};
346    use std::sync::mpsc;
347    use std::thread;
348    use std::time::Duration;
349
350    const PATIENCE: Duration = Duration::from_secs(5);
351
352    /// Checks that the transport's owners and handles print without printable
353    /// adapters, which hosts may box as trait objects.
354    #[test]
355    fn test_debug_capabilities() {
356        use crate::transport::{Attestation, Event, Roots, Sender, Server};
357
358        /// Requires a value to be printable.
359        fn printable<T: fmt::Debug>() {}
360        printable::<Stream<Box<dyn Read>, Box<dyn Write>>>();
361        printable::<Closer>();
362        printable::<Client<Box<dyn Read>, Box<dyn Write>>>();
363        printable::<Server<Box<dyn Read>, Box<dyn Write>, Attestation>>();
364        printable::<Sender<Box<dyn Write>>>();
365        printable::<Event<Box<dyn Write>>>();
366        printable::<Attestation>();
367        printable::<Roots<'static>>();
368    }
369
370    // A memory reader can deliver ready bytes after its own deadline, but the
371    // transport must reject an expired attempt before consuming those bytes.
372    #[test]
373    fn test_memory_reader_keeps_transport_deadline() {
374        let (host, ark) = crate::memory::duplex(4);
375        let (_ark_read, mut ark_write) = ark.into_halves();
376        std::io::Write::write_all(&mut ark_write, b"abc").unwrap();
377        let (reader, _writer, closer, _) = host.into_parts();
378        let mut reader = ReadHalf {
379            inner: reader,
380            closer,
381        };
382        let mut bytes = [0; 3];
383        assert_eq!(
384            reader
385                .read(&mut bytes, Some(Instant::now()))
386                .unwrap_err()
387                .kind(),
388            io::ErrorKind::TimedOut
389        );
390        assert_eq!(bytes, [0; 3]);
391        assert_eq!(reader.read(&mut bytes, None).unwrap(), 3);
392        assert_eq!(&bytes, b"abc");
393    }
394
395    /// Adapter holding an admitted call until shutdown has been requested,
396    /// then returning the result selected by the test.
397    struct Adapter {
398        entered: mpsc::Sender<()>,
399        released: mpsc::Receiver<()>,
400        fails: bool,
401        deadline: Option<Instant>,
402    }
403
404    impl Adapter {
405        /// Waits for the test's release without exceeding this adapter call's deadline.
406        fn wait(&self, deadline: Option<Instant>) -> io::Result<()> {
407            self.entered.send(()).unwrap();
408            match deadline {
409                Some(deadline) => self
410                    .released
411                    .recv_timeout(deadline.saturating_duration_since(Instant::now())),
412                None => self
413                    .released
414                    .recv()
415                    .map_err(|_| mpsc::RecvTimeoutError::Disconnected),
416            }
417            .map_err(|_| io::Error::from(io::ErrorKind::TimedOut))?;
418            if self.fails {
419                Err(io::Error::other("adapter failure"))
420            } else {
421                Ok(())
422            }
423        }
424    }
425
426    impl Read for Adapter {
427        fn set_read_deadline(&mut self, deadline: Option<Instant>) -> io::Result<()> {
428            self.deadline = deadline;
429            Ok(())
430        }
431    }
432
433    impl io::Read for Adapter {
434        fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
435            self.wait(self.deadline)?;
436            buf[0] = 0x5a;
437            Ok(1)
438        }
439    }
440
441    impl Write for Adapter {
442        fn set_write_deadline(&mut self, deadline: Instant) -> io::Result<()> {
443            self.deadline = Some(deadline);
444            Ok(())
445        }
446    }
447
448    impl io::Write for Adapter {
449        fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
450            self.wait(Some(self.deadline.expect("write deadline installed")))?;
451            Ok(buf.len())
452        }
453
454        fn flush(&mut self) -> io::Result<()> {
455            self.wait(Some(self.deadline.expect("write deadline installed")))
456        }
457    }
458
459    /// Runs an adapter call across shutdown and checks that its result survives.
460    fn during_shutdown(
461        fails: bool,
462        operation: impl FnOnce(Adapter, Closer) -> io::Result<()> + Send + 'static,
463    ) {
464        let (entered, entries) = mpsc::channel();
465        let (release, released) = mpsc::channel();
466        let closer = Closer::new(move || release.send(()).unwrap());
467        let io = thread::spawn({
468            let closer = closer.clone();
469            move || {
470                operation(
471                    Adapter {
472                        entered,
473                        released,
474                        fails,
475                        deadline: None,
476                    },
477                    closer,
478                )
479            }
480        });
481        entries.recv_timeout(PATIENCE).unwrap();
482        closer.close();
483        let result = io.join().unwrap();
484        if fails {
485            let err = result.unwrap_err();
486            assert_eq!(err.kind(), io::ErrorKind::Other);
487            assert_eq!(err.to_string(), "adapter failure");
488        } else {
489            result.unwrap();
490        }
491    }
492
493    // Tests that shutdown preserves admitted read and final flush results,
494    // including original errors. An admitted write can accept its bytes after
495    // closing starts, but the full operation then refuses the subsequent flush.
496    #[test]
497    fn test_admitted_io_preserves_results_during_shutdown() {
498        for fails in [false, true] {
499            during_shutdown(fails, |inner, closer| {
500                let mut buf = [0];
501                assert_eq!(ReadHalf { inner, closer }.read(&mut buf, None)?, 1);
502                assert_eq!(buf, [0x5a]);
503                Ok(())
504            });
505            during_shutdown(fails, move |inner, closer| {
506                let result =
507                    WriteHalf { inner, closer }.write(&[1, 2, 3], Instant::now() + PATIENCE);
508                if fails {
509                    result
510                } else {
511                    assert_eq!(result.unwrap_err().kind(), io::ErrorKind::NotConnected);
512                    Ok(())
513                }
514            });
515            during_shutdown(fails, |inner, closer| {
516                WriteHalf { inner, closer }.write(&[], Instant::now() + PATIENCE)
517            });
518        }
519    }
520
521    // Tests that handing a stream to a client transfers shutdown responsibility
522    // without closing it. Dropping either owner invokes shutdown exactly once,
523    // and repeated closes do nothing.
524    #[test]
525    fn test_ownership_and_repeated_close() {
526        let calls = Arc::new(AtomicUsize::new(0));
527        let stream = Stream::new(Memory::new(io::empty()), Memory::new(io::sink()), {
528            let calls = calls.clone();
529            move || {
530                calls.fetch_add(1, Ordering::SeqCst);
531            }
532        });
533        let closer = stream.closer();
534        let client = Client::new(stream);
535        assert_eq!(
536            calls.load(Ordering::SeqCst),
537            0,
538            "handoff must keep the stream open"
539        );
540        drop(client);
541        assert_eq!(calls.load(Ordering::SeqCst), 1);
542        closer.close();
543        drop(closer.clone());
544        assert_eq!(calls.load(Ordering::SeqCst), 1);
545
546        let stream = Stream::new(Memory::new(io::empty()), Memory::new(io::sink()), {
547            let calls = calls.clone();
548            move || {
549                calls.fetch_add(1, Ordering::SeqCst);
550            }
551        });
552        drop(stream);
553        assert_eq!(
554            calls.load(Ordering::SeqCst),
555            2,
556            "unclaimed streams also close on drop"
557        );
558    }
559
560    // Tests that concurrent close calls and owner drop all wait for shutdown.
561    // Hold the callback until every caller has started, then check that none
562    // returns before the callback is released.
563    #[test]
564    fn test_every_closer_waits_for_the_shutdown_callback() {
565        let (entered, callback) = mpsc::channel();
566        let (release, released) = mpsc::channel();
567        let stream = Stream::new(
568            Memory::new(io::empty()),
569            Memory::new(io::sink()),
570            move || {
571                entered.send(()).unwrap();
572                released.recv_timeout(PATIENCE).unwrap();
573            },
574        );
575        let closer = stream.closer();
576        let (finished, finishes) = mpsc::channel();
577        let first = thread::spawn({
578            let closer = closer.clone();
579            let finished = finished.clone();
580            move || {
581                closer.close();
582                finished.send(()).unwrap();
583            }
584        });
585        callback.recv_timeout(PATIENCE).unwrap();
586
587        let (started, starts) = mpsc::channel();
588        let second = thread::spawn({
589            let closer = closer.clone();
590            let started = started.clone();
591            let finished = finished.clone();
592            move || {
593                started.send(()).unwrap();
594                closer.close();
595                finished.send(()).unwrap();
596            }
597        });
598        let owner = thread::spawn(move || {
599            started.send(()).unwrap();
600            drop(stream);
601            finished.send(()).unwrap();
602        });
603        starts.recv_timeout(PATIENCE).unwrap();
604        starts.recv_timeout(PATIENCE).unwrap();
605        assert!(finishes.recv_timeout(Duration::from_millis(50)).is_err());
606        release.send(()).unwrap();
607        finishes.recv_timeout(PATIENCE).unwrap();
608        finishes.recv_timeout(PATIENCE).unwrap();
609        finishes.recv_timeout(PATIENCE).unwrap();
610        first.join().unwrap();
611        second.join().unwrap();
612        owner.join().unwrap();
613    }
614
615    /// Adapter operation held in flight while the test requests shutdown.
616    #[derive(Clone, Copy, Debug, PartialEq, Eq)]
617    enum BlockAt {
618        /// Hold a raw read until the test releases it.
619        Read,
620        /// Hold a raw write until the test releases it.
621        Write,
622        /// Hold a flush until the test releases it.
623        Flush,
624    }
625
626    /// Holds one adapter operation until the test releases it. This keeps I/O in
627    /// progress long enough to observe shutdown.
628    struct Gate {
629        at: BlockAt,
630        entered: mpsc::Sender<()>,
631        released: Mutex<bool>,
632        changed: Condvar,
633        calls: AtomicUsize,
634    }
635
636    impl Gate {
637        /// Holds the selected operation until released or its supplied deadline expires.
638        fn call(&self, at: BlockAt, deadline: Option<Instant>) -> io::Result<()> {
639            self.calls.fetch_add(1, Ordering::SeqCst);
640            if at == self.at {
641                self.entered.send(()).unwrap();
642                let released = self.released.lock().unwrap();
643                let released = match deadline {
644                    Some(deadline) => {
645                        self.changed
646                            .wait_timeout_while(
647                                released,
648                                deadline.saturating_duration_since(Instant::now()),
649                                |released| !*released,
650                            )
651                            .unwrap()
652                            .0
653                    }
654                    None => self
655                        .changed
656                        .wait_while(released, |released| !*released)
657                        .unwrap(),
658                };
659                if !*released {
660                    return Err(io::ErrorKind::TimedOut.into());
661                }
662            }
663            Ok(())
664        }
665
666        /// Allows the admitted operation to finish.
667        fn release(&self) {
668            *self.released.lock().unwrap() = true;
669            self.changed.notify_all();
670        }
671    }
672
673    /// Adapter half sharing a gate with the test driver.
674    struct GatedAdapter {
675        gate: Arc<Gate>,
676        deadline: Option<Instant>,
677    }
678
679    impl Read for GatedAdapter {
680        fn set_read_deadline(&mut self, deadline: Option<Instant>) -> io::Result<()> {
681            self.deadline = deadline;
682            Ok(())
683        }
684    }
685
686    impl io::Read for GatedAdapter {
687        fn read(&mut self, _: &mut [u8]) -> io::Result<usize> {
688            self.gate.call(BlockAt::Read, self.deadline)?;
689            Ok(0)
690        }
691    }
692
693    impl Write for GatedAdapter {
694        fn set_write_deadline(&mut self, deadline: Instant) -> io::Result<()> {
695            self.deadline = Some(deadline);
696            Ok(())
697        }
698    }
699
700    impl io::Write for GatedAdapter {
701        fn write(&mut self, bytes: &[u8]) -> io::Result<usize> {
702            self.gate.call(
703                BlockAt::Write,
704                Some(self.deadline.expect("write deadline installed")),
705            )?;
706            Ok(bytes.len())
707        }
708
709        fn flush(&mut self) -> io::Result<()> {
710            self.gate.call(
711                BlockAt::Flush,
712                Some(self.deadline.expect("write deadline installed")),
713            )
714        }
715    }
716
717    // Tests that close waits for an admitted read, write or flush even after
718    // the shutdown callback returns. A gate holds each operation independently;
719    // subsequent I/O must never reach the closed adapter.
720    #[test]
721    fn test_every_closer_waits_for_active_io_and_refuses_new_io() {
722        for at in [BlockAt::Read, BlockAt::Write, BlockAt::Flush] {
723            let (entered, entries) = mpsc::channel();
724            let gate = Arc::new(Gate {
725                at,
726                entered,
727                released: Mutex::new(false),
728                changed: Condvar::new(),
729                calls: AtomicUsize::new(0),
730            });
731            let (requested, requests) = mpsc::channel();
732            let stream = Stream::new(
733                GatedAdapter {
734                    gate: gate.clone(),
735                    deadline: None,
736                },
737                GatedAdapter {
738                    gate: gate.clone(),
739                    deadline: None,
740                },
741                move || {
742                    requested.send(()).unwrap();
743                },
744            );
745            let (reader, writer, closer, _) = stream.into_parts();
746            let io_closer = closer.clone();
747            let io = thread::spawn(move || {
748                let mut reader = ReadHalf {
749                    inner: reader,
750                    closer: io_closer.clone(),
751                };
752                let mut writer = WriteHalf {
753                    inner: writer,
754                    closer: io_closer,
755                };
756                let deadline = Instant::now() + PATIENCE;
757                match at {
758                    BlockAt::Read => assert_eq!(reader.read(&mut [0], None).unwrap(), 0),
759                    BlockAt::Write => assert_eq!(
760                        writer.write(&[1], deadline).unwrap_err().kind(),
761                        io::ErrorKind::NotConnected
762                    ),
763                    BlockAt::Flush => writer.write(&[], deadline).unwrap(),
764                }
765                (reader, writer)
766            });
767            entries.recv_timeout(PATIENCE).unwrap();
768            let (finished, finishes) = mpsc::channel();
769            let closing = thread::spawn({
770                let closer = closer.clone();
771                move || {
772                    closer.close();
773                    finished.send(()).unwrap();
774                }
775            });
776            requests.recv_timeout(PATIENCE).unwrap();
777            assert!(finishes.recv_timeout(Duration::from_millis(20)).is_err());
778
779            gate.release();
780            finishes.recv_timeout(PATIENCE).unwrap();
781            closing.join().unwrap();
782            let (mut reader, mut writer) = io.join().unwrap();
783            let calls = gate.calls.load(Ordering::SeqCst);
784            assert_eq!(reader.read(&mut [0], None).unwrap(), 0);
785            assert!(matches!(
786                writer.write(&[1], Instant::now() + PATIENCE),
787                Err(err) if err.kind() == io::ErrorKind::NotConnected
788            ));
789            assert_eq!(gate.calls.load(Ordering::SeqCst), calls);
790            closer.close();
791            assert!(requests.try_recv().is_err(), "shutdown called twice");
792        }
793    }
794
795    /// Accepts one byte per write and can hold flush until its supplied deadline.
796    /// Recorded deadlines reveal whether partial progress restarts the budget.
797    struct BudgetWriter {
798        bytes: Vec<u8>,
799        deadlines: Vec<Instant>,
800        stall_flush: bool,
801        deadline: Option<Instant>,
802        settings: usize,
803    }
804
805    impl Write for BudgetWriter {
806        fn set_write_deadline(&mut self, deadline: Instant) -> io::Result<()> {
807            self.deadline = Some(deadline);
808            self.settings += 1;
809            Ok(())
810        }
811    }
812
813    impl io::Write for BudgetWriter {
814        fn write(&mut self, bytes: &[u8]) -> io::Result<usize> {
815            let deadline = self.deadline.expect("write deadline installed");
816            check_deadline(deadline)?;
817            self.deadlines.push(deadline);
818            self.bytes.push(bytes[0]);
819            Ok(1)
820        }
821
822        fn flush(&mut self) -> io::Result<()> {
823            let deadline = self.deadline.expect("write deadline installed");
824            self.deadlines.push(deadline);
825            if self.stall_flush {
826                thread::sleep(deadline.saturating_duration_since(Instant::now()));
827                return Err(io::ErrorKind::TimedOut.into());
828            }
829            check_deadline(deadline)
830        }
831    }
832
833    // Tests that partial writes and a blocked flush share one absolute deadline,
834    // a timeout leaves the stream reusable, and a zero budget never calls I/O.
835    #[test]
836    fn test_output_deadline_and_reuse() {
837        let timeout = Duration::from_millis(40);
838        let stream = Stream::new(
839            Memory::new(io::empty()),
840            BudgetWriter {
841                bytes: Vec::new(),
842                deadlines: Vec::new(),
843                stall_flush: true,
844                deadline: None,
845                settings: 0,
846            },
847            || {},
848        )
849        .set_write_timeout(timeout);
850        let (_, inner, closer, configured) = stream.into_parts();
851        assert_eq!(configured, timeout);
852        let mut writer = WriteHalf { inner, closer };
853        let deadline = Instant::now() + configured;
854        assert_eq!(
855            writer.write(b"abc", deadline).unwrap_err().kind(),
856            io::ErrorKind::TimedOut
857        );
858        assert_eq!(writer.inner.deadlines, vec![deadline; 4]);
859        assert_eq!(writer.inner.settings, 1);
860
861        writer.inner.stall_flush = false;
862        let deadline = Instant::now() + PATIENCE;
863        writer.write(b"d", deadline).unwrap();
864        assert_eq!(writer.inner.bytes, b"abcd");
865        assert_eq!(writer.inner.settings, 2);
866
867        let calls = writer.inner.deadlines.len();
868        let deadline = Instant::now();
869        assert!(matches!(
870            writer.write(b"e", deadline),
871            Err(err) if err.kind() == io::ErrorKind::TimedOut
872        ));
873        assert_eq!(writer.inner.deadlines.len(), calls);
874        assert_eq!(writer.inner.settings, 2);
875        writer.closer.close();
876    }
877
878    // Tests the complete write operation with interruption and partial progress:
879    // retries retain the unsent suffix, zero progress fails without flushing,
880    // and an interrupted flush is returned directly rather than retried.
881    #[test]
882    fn test_partial_write_retries_and_failures() {
883        /// Scripts adapter results and records offered and accepted byte sequences.
884        struct Script {
885            results: std::collections::VecDeque<io::Result<usize>>,
886            offered: Vec<Vec<u8>>,
887            accepted: Vec<u8>,
888            interrupted_flush: bool,
889            flushes: usize,
890            settings: usize,
891            deadline: Option<Instant>,
892        }
893
894        impl Write for Script {
895            fn set_write_deadline(&mut self, deadline: Instant) -> io::Result<()> {
896                self.settings += 1;
897                self.deadline = Some(deadline);
898                Ok(())
899            }
900        }
901
902        impl io::Write for Script {
903            fn write(&mut self, bytes: &[u8]) -> io::Result<usize> {
904                check_deadline(self.deadline.expect("write deadline installed"))?;
905                self.offered.push(bytes.to_vec());
906                let result = self.results.pop_front().expect("unexpected write");
907                if let Ok(size) = &result {
908                    self.accepted.extend_from_slice(&bytes[..*size]);
909                }
910                result
911            }
912
913            fn flush(&mut self) -> io::Result<()> {
914                check_deadline(self.deadline.expect("write deadline installed"))?;
915                self.flushes += 1;
916                assert_eq!(self.flushes, 1, "flush retried");
917                if self.interrupted_flush {
918                    Err(io::ErrorKind::Interrupted.into())
919                } else {
920                    Ok(())
921                }
922            }
923        }
924
925        for (stalls, interrupted_flush) in [(false, false), (true, false), (false, true)] {
926            let mut writer = WriteHalf {
927                inner: Script {
928                    results: [
929                        Err(io::ErrorKind::Interrupted.into()),
930                        Ok(1),
931                        Ok(usize::from(!stalls)),
932                    ]
933                    .into(),
934                    offered: Vec::new(),
935                    accepted: Vec::new(),
936                    interrupted_flush,
937                    flushes: 0,
938                    settings: 0,
939                    deadline: None,
940                },
941                closer: Closer::new(|| {}),
942            };
943            let result = writer.write(b"ab", Instant::now() + PATIENCE);
944            if stalls {
945                assert_eq!(result.unwrap_err().kind(), io::ErrorKind::WriteZero);
946            } else if interrupted_flush {
947                assert_eq!(result.unwrap_err().kind(), io::ErrorKind::Interrupted);
948            } else {
949                result.unwrap();
950            }
951            assert_eq!(
952                writer.inner.offered,
953                [b"ab".to_vec(), b"ab".to_vec(), b"b".to_vec()]
954            );
955            assert_eq!(
956                writer.inner.accepted,
957                if stalls { &b"a"[..] } else { &b"ab"[..] }
958            );
959            assert_eq!(writer.inner.flushes, usize::from(!stalls));
960            assert_eq!(writer.inner.settings, 1);
961            writer.closer.close();
962        }
963    }
964
965    // Tests that a failed deadline setter prevents byte I/O and preserves its
966    // error, including Interrupted and TimedOut. Only retryable errors from an
967    // actual read may start another attempt.
968    #[test]
969    fn test_deadline_setter_failure_prevents_io() {
970        /// Rejects deadline installation and panics if byte I/O is attempted.
971        struct Refused {
972            settings: usize,
973            kind: io::ErrorKind,
974        }
975
976        impl Refused {
977            /// Fails once so an incorrect retry fails the test promptly.
978            fn reject(&mut self) -> io::Result<()> {
979                self.settings += 1;
980                assert_eq!(self.settings, 1, "deadline setter failure retried");
981                Err(io::Error::new(self.kind, "deadline refused"))
982            }
983        }
984
985        impl Read for Refused {
986            fn set_read_deadline(&mut self, _: Option<Instant>) -> io::Result<()> {
987                self.reject()
988            }
989        }
990
991        impl io::Read for Refused {
992            fn read(&mut self, _: &mut [u8]) -> io::Result<usize> {
993                panic!("read after deadline setter failed")
994            }
995        }
996
997        impl Write for Refused {
998            fn set_write_deadline(&mut self, _: Instant) -> io::Result<()> {
999                self.reject()
1000            }
1001        }
1002
1003        impl io::Write for Refused {
1004            fn write(&mut self, _: &[u8]) -> io::Result<usize> {
1005                panic!("write after deadline setter failed")
1006            }
1007
1008            fn flush(&mut self) -> io::Result<()> {
1009                panic!("flush after deadline setter failed")
1010            }
1011        }
1012
1013        for kind in [io::ErrorKind::Interrupted, io::ErrorKind::TimedOut] {
1014            let closer = Closer::new(|| {});
1015            let mut reader = ReadHalf {
1016                inner: Refused { settings: 0, kind },
1017                closer: closer.clone(),
1018            };
1019            let err = reader.read(&mut [0], None).unwrap_err();
1020            assert_eq!(err.kind(), kind);
1021            assert_eq!(err.to_string(), "deadline refused");
1022            let mut writer = WriteHalf {
1023                inner: Refused { settings: 0, kind },
1024                closer: closer.clone(),
1025            };
1026            assert!(matches!(
1027                writer.write(&[1], Instant::now() + PATIENCE),
1028                Err(err) if err.kind() == kind && err.to_string() == "deadline refused"
1029            ));
1030
1031            closer.close();
1032            assert_eq!(reader.read(&mut [0], None).unwrap(), 0);
1033            assert!(matches!(
1034                writer.write(&[1], Instant::now() + PATIENCE),
1035                Err(err) if err.kind() == io::ErrorKind::NotConnected
1036            ));
1037            assert_eq!(reader.inner.settings, 1);
1038            assert_eq!(writer.inner.settings, 1);
1039        }
1040    }
1041
1042    // Tests that a partial write returning after its deadline leaves its
1043    // accepted byte intact but fails the complete operation. Depending on the
1044    // input length, either the remaining write or flush is refused before I/O.
1045    #[test]
1046    fn test_late_write_preserves_progress() {
1047        /// Accepts one byte but delays returning until its installed deadline.
1048        #[derive(Default)]
1049        struct LateWriter {
1050            deadline: Option<Instant>,
1051            bytes: Vec<u8>,
1052        }
1053
1054        impl Write for LateWriter {
1055            fn set_write_deadline(&mut self, deadline: Instant) -> io::Result<()> {
1056                self.deadline = Some(deadline);
1057                Ok(())
1058            }
1059        }
1060
1061        impl io::Write for LateWriter {
1062            fn write(&mut self, bytes: &[u8]) -> io::Result<usize> {
1063                self.bytes.push(bytes[0]);
1064                thread::sleep(
1065                    self.deadline
1066                        .expect("write deadline installed")
1067                        .saturating_duration_since(Instant::now()),
1068                );
1069                Ok(1)
1070            }
1071
1072            fn flush(&mut self) -> io::Result<()> {
1073                panic!("flush admitted after deadline expired")
1074            }
1075        }
1076
1077        for bytes in [&b"a"[..], &b"ab"[..]] {
1078            let mut writer = WriteHalf {
1079                inner: LateWriter::default(),
1080                closer: Closer::new(|| {}),
1081            };
1082            let deadline = Instant::now() + Duration::from_millis(40);
1083            assert_eq!(
1084                writer.write(bytes, deadline).unwrap_err().kind(),
1085                io::ErrorKind::TimedOut
1086            );
1087            assert_eq!(writer.inner.bytes, b"a");
1088            writer.closer.close();
1089        }
1090    }
1091}