monocoque_core/timeout.rs
1//! Timeout utilities for I/O operations
2//!
3//! Provides timeout wrappers for async read/write operations using compio's timeout support.
4
5use crate::rt::timeout;
6use compio_io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt};
7use std::io;
8use std::time::Duration;
9
10/// Execute an async `read_exact` operation with a timeout.
11///
12/// Reads exactly the full buffer or returns an error.
13pub async fn read_exact_with_timeout<S, B>(
14 stream: &mut S,
15 buf: B,
16 duration: Option<Duration>,
17) -> io::Result<compio_buf::BufResult<(), B>>
18where
19 S: AsyncRead + Unpin,
20 B: compio_buf::IoBufMut,
21{
22 match duration {
23 None => {
24 // No timeout, block indefinitely
25 Ok(stream.read_exact(buf).await)
26 }
27 Some(d) if d.is_zero() => {
28 // A zero budget cannot complete an exact read. These helpers serve
29 // the handshake, where that means the step is out of time; report a
30 // timeout rather than pretend a non-blocking read is in progress.
31 // (User-facing RCVTIMEO=0 non-blocking recv is handled in
32 // SocketBase::read_raw, which returns WouldBlock after draining any
33 // already-buffered frames - it never reaches this helper.)
34 Err(io::Error::new(
35 io::ErrorKind::TimedOut,
36 "Read operation timed out (zero timeout budget)",
37 ))
38 }
39 Some(d) => {
40 // Timeout mode
41 match timeout(d, stream.read_exact(buf)).await {
42 Ok(result) => Ok(result),
43 Err(_elapsed) => Err(io::Error::new(
44 io::ErrorKind::TimedOut,
45 "Read operation timed out",
46 )),
47 }
48 }
49 }
50}
51
52/// Execute an async `write_all` operation with a timeout.
53///
54/// Writes the entire buffer or returns an error.
55pub async fn write_all_with_timeout<S, B>(
56 stream: &mut S,
57 buf: B,
58 duration: Option<Duration>,
59) -> io::Result<compio_buf::BufResult<(), B>>
60where
61 S: AsyncWrite + Unpin,
62 B: compio_buf::IoBuf,
63{
64 match duration {
65 None => {
66 // No timeout, block indefinitely
67 Ok(stream.write_all(buf).await)
68 }
69 Some(d) if d.is_zero() => {
70 // A zero budget cannot complete a full write. See the read helper:
71 // in the handshake this means the step is out of time. User-facing
72 // SNDTIMEO=0 non-blocking send is handled in SocketBase, not here.
73 Err(io::Error::new(
74 io::ErrorKind::TimedOut,
75 "Write operation timed out (zero timeout budget)",
76 ))
77 }
78 Some(d) => {
79 // Timeout mode
80 match timeout(d, stream.write_all(buf)).await {
81 Ok(result) => Ok(result),
82 Err(_elapsed) => Err(io::Error::new(
83 io::ErrorKind::TimedOut,
84 "Write operation timed out",
85 )),
86 }
87 }
88 }
89}
90
91#[cfg(test)]
92mod tests {
93 use super::*;
94
95 // Note: These are compile-time tests to ensure the API is sound
96 // Full integration tests would require actual I/O operations
97
98 #[test]
99 fn test_timeout_types() {
100 let infinite: Option<Duration> = None;
101 assert!(infinite.is_none());
102 let nonblocking = Some(Duration::ZERO);
103 assert_eq!(nonblocking, Some(Duration::ZERO));
104 let timed = Some(Duration::from_secs(5));
105 assert_eq!(timed, Some(Duration::from_secs(5)));
106 }
107}