use super::*;
use tokio::io::{AsyncReadExt as _, AsyncWriteExt as _};
fn duplex_pair() -> (tokio::io::DuplexStream, tokio::io::DuplexStream) {
tokio::io::duplex(64 * 1024)
}
async fn socket_pair() -> (tokio::net::TcpStream, tokio::net::TcpStream) {
let listener = tokio::net::TcpListener::bind("127.0.0.1:0")
.await
.expect("bind loopback");
let addr = listener.local_addr().expect("local addr");
let connect = tokio::spawn(async move { tokio::net::TcpStream::connect(addr).await });
let (server, _) = listener.accept().await.expect("accept");
let client = connect.await.expect("join").expect("connect");
(server, client)
}
#[tokio::test(start_paused = true)]
async fn a_silent_peer_is_disconnected() {
let (server, _client) = duplex_pair();
let mut io = IdleTimeout::new(server, Some(Duration::from_secs(75)));
let mut buf = [0_u8; 64];
let err = tokio::time::timeout(Duration::from_secs(600), io.read(&mut buf))
.await
.expect("the idle deadline was never enforced; the read hung")
.expect_err("a connection that never speaks must not be held open");
assert_eq!(err.kind(), std::io::ErrorKind::TimedOut);
assert!(
err.to_string().contains("idle"),
"the error should say why the connection was closed, got: {err}"
);
}
#[tokio::test(start_paused = true)]
async fn a_slow_but_progressing_peer_is_not_disconnected() {
let (server, mut client) = duplex_pair();
let mut io = IdleTimeout::new(server, Some(Duration::from_secs(75)));
tokio::spawn(async move {
for i in 0..5_u8 {
tokio::time::sleep(Duration::from_secs(60)).await;
client.write_all(&[i]).await.expect("peer write");
}
tokio::time::sleep(Duration::from_secs(3600)).await;
});
for expected in 0..5_u8 {
let mut buf = [0_u8; 1];
io.read_exact(&mut buf)
.await
.unwrap_or_else(|e| panic!("byte {expected} should arrive, got: {e}"));
assert_eq!(buf[0], expected);
}
}
#[tokio::test(start_paused = true)]
async fn a_write_only_stream_stays_alive() {
let (server, mut client) = duplex_pair();
let mut io = IdleTimeout::new(server, Some(Duration::from_secs(75)));
tokio::spawn(async move {
let mut sink = vec![0_u8; 1024];
loop {
if client.read(&mut sink).await.unwrap_or(0) == 0 {
break;
}
}
});
let mut buf = [0_u8; 64];
for i in 0..10_u8 {
tokio::select! {
read = io.read(&mut buf) => {
let n = read.unwrap_or_else(|e| panic!(
"the pending read failed at frame {i}, so an outbound-only \
stream was treated as idle: {e}"
));
assert_eq!(n, 0, "the peer never writes, so any read is EOF");
break;
}
() = tokio::time::sleep(Duration::from_secs(30)) => {
io.write_all(&[i])
.await
.unwrap_or_else(|e| panic!("frame {i} should be writable: {e}"));
}
}
}
}
#[tokio::test(start_paused = true)]
async fn activity_does_not_buy_permanent_immunity() {
let (server, mut client) = duplex_pair();
let mut io = IdleTimeout::new(server, Some(Duration::from_secs(75)));
client.write_all(b"hello").await.expect("peer write");
let mut buf = [0_u8; 5];
io.read_exact(&mut buf).await.expect("first read arrives");
tokio::spawn(async move {
tokio::time::sleep(Duration::from_secs(3600)).await;
drop(client);
});
let err = io
.read(&mut buf)
.await
.expect_err("going quiet after being busy still ends the connection");
assert_eq!(err.kind(), std::io::ErrorKind::TimedOut);
}
#[tokio::test(start_paused = true)]
async fn end_of_stream_reads_as_eof_not_activity() {
let (server, client) = duplex_pair();
let mut io = IdleTimeout::new(server, Some(Duration::from_secs(75)));
let armed = io.deadline.deadline();
tokio::time::sleep(Duration::from_secs(10)).await;
drop(client);
let mut buf = [0_u8; 64];
let n = io.read(&mut buf).await.expect("a closed peer reads as EOF");
assert_eq!(n, 0, "EOF, reported as EOF rather than as a timeout");
assert_eq!(
io.deadline.deadline(),
armed,
"EOF re-armed the idle timer, so a half-closed peer would hold the \
connection open forever by saying nothing"
);
}
#[tokio::test]
async fn vectored_write_support_is_reported_from_the_socket() {
let (server, _client) = socket_pair().await;
let expected = server.is_write_vectored();
let io = IdleTimeout::new(server, Some(Duration::from_secs(75)));
assert_eq!(io.is_write_vectored(), expected);
}
#[tokio::test(start_paused = true)]
async fn a_zero_length_write_does_not_count_as_activity() {
let (server, mut client) = duplex_pair();
let mut io = IdleTimeout::new(server, Some(Duration::from_secs(75)));
tokio::spawn(async move {
let mut sink = vec![0_u8; 1024];
loop {
if client.read(&mut sink).await.unwrap_or(0) == 0 {
break;
}
}
});
let mut buf = [0_u8; 64];
let outcome = tokio::time::timeout(Duration::from_secs(600), async {
loop {
tokio::select! {
read = io.read(&mut buf) => break read,
() = tokio::time::sleep(Duration::from_secs(30)) => {
let n = io.write(&[]).await.expect("an empty write is writable");
assert_eq!(n, 0, "nothing to write means nothing written");
}
}
}
})
.await
.expect("empty writes kept the connection alive: the idle deadline never fired");
let err = outcome.expect_err("a peer that only sends empty writes is idle");
assert_eq!(err.kind(), std::io::ErrorKind::TimedOut);
}
#[tokio::test(start_paused = true)]
async fn vectored_writes_count_as_activity() {
let (server, mut client) = duplex_pair();
let mut io = IdleTimeout::new(server, Some(Duration::from_secs(75)));
tokio::spawn(async move {
let mut sink = vec![0_u8; 1024];
loop {
if client.read(&mut sink).await.unwrap_or(0) == 0 {
break;
}
}
});
let mut buf = [0_u8; 64];
for i in 0..10_u8 {
tokio::select! {
read = io.read(&mut buf) => {
let n = read.unwrap_or_else(|e| panic!(
"the pending read failed at frame {i}, so vectored writes \
were not counted as activity: {e}"
));
assert_eq!(n, 0, "the peer never writes, so any read is EOF");
break;
}
() = tokio::time::sleep(Duration::from_secs(30)) => {
let byte = [i];
let bufs = [std::io::IoSlice::new(&byte), std::io::IoSlice::new(&byte)];
let n = io.write_vectored(&bufs)
.await
.unwrap_or_else(|e| panic!("frame {i} should be writable: {e}"));
assert!(n > 0, "the vectored write must move bytes");
}
}
}
}
#[tokio::test(start_paused = true)]
async fn a_zero_length_vectored_write_does_not_count_as_activity() {
let (server, mut client) = duplex_pair();
let mut io = IdleTimeout::new(server, Some(Duration::from_secs(75)));
tokio::spawn(async move {
let mut sink = vec![0_u8; 1024];
loop {
if client.read(&mut sink).await.unwrap_or(0) == 0 {
break;
}
}
});
let mut buf = [0_u8; 64];
let outcome = tokio::time::timeout(Duration::from_secs(600), async {
loop {
tokio::select! {
read = io.read(&mut buf) => break read,
() = tokio::time::sleep(Duration::from_secs(30)) => {
let n = io.write_vectored(&[]).await.expect("an empty vectored write is writable");
assert_eq!(n, 0, "nothing to write means nothing written");
}
}
}
})
.await
.expect("empty vectored writes kept the connection alive: the idle deadline never fired");
let err = outcome.expect_err("a peer that only sends empty vectored writes is idle");
assert_eq!(err.kind(), std::io::ErrorKind::TimedOut);
}
#[derive(Default)]
struct Recorder {
flushes: usize,
shutdowns: usize,
}
impl AsyncRead for Recorder {
fn poll_read(
self: Pin<&mut Self>,
_cx: &mut Context<'_>,
_buf: &mut ReadBuf<'_>,
) -> Poll<io::Result<()>> {
Poll::Pending
}
}
impl AsyncWrite for Recorder {
fn poll_write(
self: Pin<&mut Self>,
_cx: &mut Context<'_>,
buf: &[u8],
) -> Poll<io::Result<usize>> {
Poll::Ready(Ok(buf.len()))
}
fn poll_flush(mut self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<io::Result<()>> {
self.flushes += 1;
Poll::Ready(Ok(()))
}
fn poll_shutdown(mut self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<io::Result<()>> {
self.shutdowns += 1;
Poll::Ready(Ok(()))
}
fn is_write_vectored(&self) -> bool {
false
}
}
#[tokio::test]
async fn flush_and_shutdown_are_forwarded_to_the_transport() {
let mut io = IdleTimeout::new(Recorder::default(), Some(Duration::from_secs(75)));
io.flush().await.expect("flush");
assert_eq!(io.inner.flushes, 1, "flush must reach the transport");
assert_eq!(io.inner.shutdowns, 0);
io.shutdown().await.expect("shutdown");
assert_eq!(io.inner.shutdowns, 1, "shutdown must reach the transport");
assert_eq!(
io.inner.flushes, 1,
"shutdown does not flush on the wrapper's behalf"
);
}
#[tokio::test]
async fn a_transport_without_vectored_writes_is_reported_as_such() {
let io = IdleTimeout::new(Recorder::default(), Some(Duration::from_secs(75)));
assert!(!io.is_write_vectored());
}