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