Read and Write Timeouts
Read and write timeouts prevent blocking socket operations from hanging indefinitely when a remote peer crashes silently, a firewall drops packets without sending a TCP RST, or a network link becomes congested. Both TcpStream and UdpSocket configure socket-level timeouts using Option<std::time::Duration>.
Timeout Configuration Methods
| Method | Signature | Description |
|---|---|---|
.set_read_timeout(dur) |
set_read_timeout(&self, dur: Option<Duration>) -> io::Result<()> |
Sets the maximum wait time for a single read/recv call (None disables the timeout). |
.set_write_timeout(dur) |
set_write_timeout(&self, dur: Option<Duration>) -> io::Result<()> |
Sets the maximum wait time for a single write/send call (None disables the timeout). |
.read_timeout() |
read_timeout(&self) -> io::Result<Option<Duration>> |
Queries the currently configured read timeout. |
.write_timeout() |
write_timeout(&self) -> io::Result<Option<Duration>> |
Queries the currently configured write timeout. |
TcpStream::connect_timeout(&addr, dur) |
connect_timeout(addr: &SocketAddr, timeout: Duration) -> io::Result<TcpStream> |
Bounds the initial TCP handshake duration. |
Passing Some(Duration::ZERO) to set_read_timeout or set_write_timeout is rejected at runtime with Err(io::ErrorKind::InvalidInput).
Handling Timeout Errors Across Platforms
When a read or write timeout expires on a blocking socket, the returned io::Error has a kind of either ErrorKind::WouldBlock (common on Unix where SO_RCVTIMEO returns EAGAIN/EWOULDBLOCK) or ErrorKind::TimedOut (common on Windows where WSAETIMEDOUT is returned). Always match both ErrorKind::WouldBlock | ErrorKind::TimedOut when checking for socket timeouts:
use std::io::{self, ErrorKind, Read};
use std::net::{TcpListener, TcpStream};
use std::time::Duration;
fn is_timeout(err: &io::Error) -> bool {
matches!(err.kind(), ErrorKind::WouldBlock | ErrorKind::TimedOut)
}
fn main() -> io::Result<()> {
let listener = TcpListener::bind("127.0.0.1:0")?;
let addr = listener.local_addr()?;
let mut client = TcpStream::connect_timeout(&addr, Duration::from_secs(1))?;
let (_server_idle, _) = listener.accept()?;
// Configure a 25ms read timeout on the client
let timeout = Duration::from_millis(25);
client.set_read_timeout(Some(timeout))?;
assert_eq!(client.read_timeout()?, Some(timeout));
// `_server_idle` stays open without sending any bytes, triggering a read timeout
let mut buf = [0u8; 32];
match client.read(&mut buf) {
Ok(n) => println!("Unexpectedly read {n} bytes"),
Err(ref e) if is_timeout(e) => {
println!("Read timed out cleanly after 25ms");
}
Err(e) => return Err(e),
}
// Clear the read timeout by passing `None`
client.set_read_timeout(None)?;
assert_eq!(client.read_timeout()?, None);
Ok(())
}
Read timed out cleanly after 25ms
Per-Syscall Timeout vs Total Operation Deadline
set_read_timeout and set_write_timeout apply to each individual underlying OS read or write system call, not to composite methods like read_to_end, read_exact, or write_all. If an attacker trickles 1 byte every 9 seconds against a 10-second set_read_timeout while the server calls read_to_end, the connection can stay open indefinitely (a "Slowloris" attack). Enforce an overall wall-clock Instant deadline across multi-read loops to defend against trickle attacks.