use std::future::Future as _;
use std::io;
use std::pin::Pin;
use std::task::{Context, Poll};
use std::time::Duration;
use tokio::io::{AsyncRead, AsyncWrite, ReadBuf};
use tokio::time::{Instant, Sleep};
pub(super) struct IdleTimeout<T> {
inner: T,
idle: Option<Duration>,
deadline: Pin<Box<Sleep>>,
}
impl<T> IdleTimeout<T> {
pub(super) fn new(inner: T, idle: Option<Duration>) -> Self {
Self {
inner,
idle,
deadline: Box::pin(tokio::time::sleep(idle.unwrap_or(Duration::ZERO))),
}
}
fn touch(&mut self) {
let Some(idle) = self.idle else {
return;
};
match Instant::now().checked_add(idle) {
Some(next) => self.deadline.as_mut().reset(next),
None => self.idle = None,
}
}
fn timed_out(&self) -> io::Error {
io::Error::new(
io::ErrorKind::TimedOut,
format!(
"connection idle for more than {:?}; closing to release the task and descriptor",
self.idle.unwrap_or_default()
),
)
}
}
impl<T: AsyncRead + Unpin> AsyncRead for IdleTimeout<T> {
fn poll_read(
mut self: Pin<&mut Self>,
cx: &mut Context<'_>,
buf: &mut ReadBuf<'_>,
) -> Poll<io::Result<()>> {
let before = buf.filled().len();
match Pin::new(&mut self.inner).poll_read(cx, buf) {
Poll::Ready(Ok(())) => {
if buf.filled().len() > before {
self.touch();
}
Poll::Ready(Ok(()))
}
Poll::Ready(Err(e)) => Poll::Ready(Err(e)),
Poll::Pending => {
if self.idle.is_some() && self.deadline.as_mut().poll(cx).is_ready() {
return Poll::Ready(Err(self.timed_out()));
}
Poll::Pending
}
}
}
}
impl<T: AsyncWrite + Unpin> AsyncWrite for IdleTimeout<T> {
fn poll_write(
mut self: Pin<&mut Self>,
cx: &mut Context<'_>,
buf: &[u8],
) -> Poll<io::Result<usize>> {
let result = Pin::new(&mut self.inner).poll_write(cx, buf);
if let Poll::Ready(Ok(n)) = result {
if n > 0 {
self.touch();
}
}
result
}
fn poll_flush(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<()>> {
Pin::new(&mut self.inner).poll_flush(cx)
}
fn poll_shutdown(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<()>> {
Pin::new(&mut self.inner).poll_shutdown(cx)
}
fn poll_write_vectored(
mut self: Pin<&mut Self>,
cx: &mut Context<'_>,
bufs: &[io::IoSlice<'_>],
) -> Poll<io::Result<usize>> {
let result = Pin::new(&mut self.inner).poll_write_vectored(cx, bufs);
if let Poll::Ready(Ok(n)) = result {
if n > 0 {
self.touch();
}
}
result
}
fn is_write_vectored(&self) -> bool {
self.inner.is_write_vectored()
}
}
#[cfg(test)]
mod tests;