{"record":{"id":"06003a629bf6d058","repo":"transact-rs/sqlx","slug":"expected-to-read-bytes-got-bytes-at-eof","errorCode":null,"errorMessage":"expected to read {} bytes, got {} bytes at EOF","messagePattern":"expected to read (.+?) bytes, got (.+?) bytes at EOF","errorType":"exception","errorClass":"io::Error (UnexpectedEof)","httpStatus":null,"severity":"error","filePath":"sqlx-core/src/net/socket/buffered.rs","lineNumber":287,"sourceCode":"            self.bytes_flushed = 0;\n            self.bytes_written = 0;\n        }\n\n        self.sanity_check();\n    }\n}\n\nimpl ReadBuffer {\n    async fn read(&mut self, len: usize, socket: &mut impl Socket) -> io::Result<()> {\n        // Because of how `BytesMut` works, we should only be shifting capacity back and forth\n        // between `read` and `available` unless we have to read an oversize message.\n        while self.read.len() < len {\n            self.reserve(len - self.read.len());\n\n            let read = socket.read(&mut self.available).await?;\n\n            if read == 0 {\n                return Err(io::Error::new(\n                    io::ErrorKind::UnexpectedEof,\n                    format!(\n                        \"expected to read {} bytes, got {} bytes at EOF\",\n                        len,\n                        self.read.len()\n                    ),\n                ));\n            }\n\n            self.advance(read);\n        }\n\n        Ok(())\n    }\n\n    fn reserve(&mut self, amt: usize) {\n        if let Some(additional) = amt.checked_sub(self.available.capacity()) {\n            self.available.reserve(additional);","sourceCodeStart":269,"sourceCodeEnd":305,"githubUrl":"https://github.com/transact-rs/sqlx/blob/03af8bcc5711a1935580a54bea249c219a0c217d/sqlx-core/src/net/socket/buffered.rs#L269-L305","documentation":"sqlx's buffered socket layer accumulates exactly `len` bytes before handing them to the protocol decoder. If the underlying socket returns 0 bytes (EOF) while the buffer still holds fewer than `len` bytes, it raises `UnexpectedEof` reporting how many bytes were expected versus received. This means the peer closed the connection mid-message.","triggerScenarios":"Any `read` on a buffered socket where the remote side closes the TCP/UDS stream before `len` bytes of an in-flight message arrive — truncated server response, abrupt disconnect, proxy/load-balancer timeout cutting the stream.","commonSituations":"Database server restarted or crashed mid-query; connection idle-killed by a firewall or LB between client and DB; network interruption; reading past the end of a half-closed connection.","solutions":["Check database server logs for crashes/restarts around the time of the error","Enable TCP keepalive / set idle timeouts so stale connections are detected before use","Reconnect and retry the operation; wrap long-lived connections with a health check (e.g. `pool.acquire` + ping)","Inspect proxies/firewalls/load balancers for idle connection limits"],"exampleFix":"// before: reusing a long-idle connection that the server already closed\nlet row = conn.fetch_one(query).await?;\n// after: use a pool that validates connections before use\nlet pool = PoolOptions::<MySql>::new().after_connect(|c| ...).test_before_acquire(true);\nlet row = pool.acquire().await?.fetch_one(query).await?;","handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"match conn.fetch_one(query).await {\n    Err(e) if e.as_database_error().map(|d| d.to_string().contains(\"EOF\")).unwrap_or(false)\n        || matches!(e.source(), Some(s) if s.to_string().contains(\"UnexpectedEof\")) => {\n        // drop connection, reconnect via pool, retry once\n    }\n    other => other?,\n}","preventionTips":["Use a connection pool with test_before_acquire and reasonable idle timeouts","Enable TCP keepalive on long-lived connections","Check DB server logs and LB/firewall idle-kill settings","Wrap flaky reads in a bounded retry with backoff"],"tags":["rust","sqlx","io","eof","network","socket"],"backgroundTag":"unexpected-eof","analyzedSha":"03af8bcc5711a1935580a54bea249c219a0c217d","analyzedAt":"2026-09-03T15:01:28.752Z","contentChangedAt":"2026-09-03T15:01:28.752Z","schemaVersion":2},"datasetVersion":"2026-09-10T17:17:09.494Z"}