herdrdev/herdr · error · io::Error
timed out reading api request
Error message
timed out reading api request
What it means
While reading a client's initial request line, the server polls the local stream in small intervals (CONNECTION_POLL_INTERVAL). If the stream stays Pending (no data) until the deadline, the read aborts with ErrorKind::TimedOut and 'timed out reading api request'. This bounds how long a connected-but-silent client can hold the reader. It fires only before any complete line has arrived.
Source
Thrown at src/api/server.rs:543
match read {
LocalStreamRead::Closed => break Ok(None),
LocalStreamRead::Data => {
bytes.push(byte[0]);
if byte[0] == b'\n' {
break String::from_utf8(bytes)
.map(Some)
.map_err(|err| io::Error::new(io::ErrorKind::InvalidData, err));
}
if bytes.len() > max_bytes {
break Err(io::Error::new(
io::ErrorKind::InvalidData,
"api request line is too large",
));
}
}
LocalStreamRead::Pending => {
if Instant::now() >= deadline {
break Err(io::Error::new(
io::ErrorKind::TimedOut,
"timed out reading api request",
));
}
std::thread::sleep(CONNECTION_POLL_INTERVAL);
}
}
};
set_local_stream_polling(stream, false)?;
result
}
#[cfg(all(test, windows))]
mod windows_tests {
use super::*;
use interprocess::local_socket::traits::Listener as _;
use std::io::{BufRead, BufReader};
use std::sync::mpsc::{self, Receiver};View on GitHub (pinned to f457cff4f2)
Solutions
- Make the client send its complete newline-terminated request line immediately after connecting.
- If the client legitimately waits (e.g. user input), send the request only after it is ready rather than connecting early and idling.
- Increase the timeout passed to read_initial_request_line_with_timeout if slow clients are expected.
- Verify the client and server agree on the line framing (trailing newline required).
Example fix
// before let mut stream = UnixStream::connect(path)?; thread::sleep(Duration::from_secs(30)); stream.write_all(req.as_bytes())?; // after let mut stream = UnixStream::connect(path)?; stream.write_all(req.as_bytes())?; stream.write_all(b"\n")?;
Defensive patterns
Strategy: retry
Validate before calling
let mut stream = UnixStream::connect(path)?; stream.set_write_timeout(Some(Duration::from_secs(5)))?; // write the full line immediately after connect, before anything else stream.write_all(request_line_with_newline.as_bytes())?;
Try / catch
match read_initial_request_line_with_timeout(&mut stream, timeout) {
Err(e) if e.kind() == io::ErrorKind::TimedOut => {
// client never sent data: reconnect and resend once
}
other => other,
} Prevention
- Send the complete request immediately after connecting; never connect and idle.
- Confirm the client appends the trailing newline the server needs.
- Close sockets promptly on client shutdown so no half-open connections linger.
When it happens
Trigger: A client opens the local API socket but sends no bytes (or no newline-terminated line) before the read deadline in read_initial_request_line_with_limits; e.g. connect() without a write, a paused client, or a probe tool that just opens the socket.
Common situations: Health-check scripts that only open a connection; a client blocked on its own I/O before sending; network/VM file-descriptor stalls on the local socket; leftover sockets from a crashed client.
Understand the failure class
- Timeouts: ETIMEDOUT, deadlines, and hung requests — what actually expires when a request times out.
Related errors
- api request line is too large
- timed out waiting for app response after {} ms
- app response channel closed
- timed out reading stream frame header
- timed out reading stream frame body
AI-assisted analysis of herdrdev/herdr@f457cff4f2 (2026-08-28).
Data as JSON: /api/errors/d796f80875d09b4f.
Report an issue: GitHub.