use futures::task::AtomicWaker;
use parking_lot::Mutex;
use std::future::Future;
use std::io;
use std::pin::Pin;
use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::Arc;
use std::task::{Context, Poll};
use std::time::Duration;
use tokio::io::{AsyncRead, AsyncWrite, ReadBuf};
use tokio::sync::OwnedSemaphorePermit;
use tokio::time::{Instant, Sleep};
pub(crate) struct ConnActivity {
in_flight: AtomicUsize,
idle_since: Mutex<Instant>,
waker: AtomicWaker,
_slot: Option<OwnedSemaphorePermit>,
}
impl ConnActivity {
pub(crate) fn new(slot: Option<OwnedSemaphorePermit>) -> Arc<Self> {
Arc::new(Self {
in_flight: AtomicUsize::new(0),
idle_since: Mutex::new(Instant::now()),
waker: AtomicWaker::new(),
_slot: slot,
})
}
}
pub struct InFlight {
activity: Arc<ConnActivity>,
}
impl InFlight {
pub(crate) fn new(activity: &Arc<ConnActivity>) -> Self {
activity.in_flight.fetch_add(1, Ordering::AcqRel);
Self {
activity: activity.clone(),
}
}
}
impl Clone for InFlight {
fn clone(&self) -> Self {
Self::new(&self.activity)
}
}
impl Drop for InFlight {
fn drop(&mut self) {
*self.activity.idle_since.lock() = Instant::now();
if self.activity.in_flight.fetch_sub(1, Ordering::AcqRel) == 1 {
self.activity.waker.wake();
}
}
}
impl std::fmt::Debug for InFlight {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("InFlight").finish_non_exhaustive()
}
}
pub(crate) struct IdleIo<T> {
inner: T,
activity: Arc<ConnActivity>,
timeout: Duration,
sleep: Pin<Box<Sleep>>,
}
impl<T> IdleIo<T> {
pub(crate) fn new(inner: T, activity: Arc<ConnActivity>, timeout: Duration) -> Self {
let deadline = *activity.idle_since.lock() + timeout;
Self {
inner,
activity,
timeout,
sleep: Box::pin(tokio::time::sleep_until(deadline)),
}
}
fn poll_idle(&mut self, cx: &mut Context<'_>) -> io::Result<()> {
self.activity.waker.register(cx.waker());
if self.activity.in_flight.load(Ordering::Acquire) > 0 {
return Ok(());
}
let deadline = *self.activity.idle_since.lock() + self.timeout;
if self.sleep.deadline() != deadline {
self.sleep.as_mut().reset(deadline);
}
match self.sleep.as_mut().poll(cx) {
Poll::Ready(()) => Err(io::Error::new(
io::ErrorKind::TimedOut,
"connection idle for longer than server.idle_timeout_secs",
)),
Poll::Pending => Ok(()),
}
}
}
impl<T: AsyncRead + Unpin> AsyncRead for IdleIo<T> {
fn poll_read(
self: Pin<&mut Self>,
cx: &mut Context<'_>,
buf: &mut ReadBuf<'_>,
) -> Poll<io::Result<()>> {
let this = self.get_mut();
this.poll_idle(cx)?;
Pin::new(&mut this.inner).poll_read(cx, buf)
}
}
impl<T: AsyncWrite + Unpin> AsyncWrite for IdleIo<T> {
fn poll_write(
self: Pin<&mut Self>,
cx: &mut Context<'_>,
buf: &[u8],
) -> Poll<io::Result<usize>> {
let this = self.get_mut();
this.poll_idle(cx)?;
Pin::new(&mut this.inner).poll_write(cx, buf)
}
fn poll_write_vectored(
self: Pin<&mut Self>,
cx: &mut Context<'_>,
bufs: &[io::IoSlice<'_>],
) -> Poll<io::Result<usize>> {
let this = self.get_mut();
this.poll_idle(cx)?;
Pin::new(&mut this.inner).poll_write_vectored(cx, bufs)
}
fn is_write_vectored(&self) -> bool {
self.inner.is_write_vectored()
}
fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<()>> {
Pin::new(&mut self.get_mut().inner).poll_flush(cx)
}
fn poll_shutdown(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<()>> {
Pin::new(&mut self.get_mut().inner).poll_shutdown(cx)
}
}
#[cfg(test)]
mod tests {
use super::*;
use tokio::io::{AsyncReadExt, AsyncWriteExt};
#[tokio::test(start_paused = true)]
async fn an_idle_connection_times_out() {
let (client, server) = tokio::io::duplex(64);
let activity = ConnActivity::new(None);
let mut io = IdleIo::new(server, activity, Duration::from_secs(5));
let mut buf = [0u8; 8];
let err = io.read(&mut buf).await.unwrap_err();
assert_eq!(err.kind(), io::ErrorKind::TimedOut);
drop(client);
}
#[tokio::test(start_paused = true)]
async fn a_request_in_flight_holds_it_open_and_its_end_restarts_the_clock() {
let (mut client, server) = tokio::io::duplex(64);
let activity = ConnActivity::new(None);
let mut io = IdleIo::new(server, activity.clone(), Duration::from_secs(5));
let request = InFlight::new(&activity);
let reader = tokio::spawn(async move {
let mut buf = [0u8; 8];
let started = Instant::now();
let n = io.read(&mut buf).await.unwrap();
assert_eq!(n, 1);
let err = io.read(&mut buf).await.unwrap_err();
(started.elapsed(), err.kind())
});
tokio::time::sleep(Duration::from_secs(60)).await;
client.write_all(b"x").await.unwrap();
tokio::time::sleep(Duration::from_secs(1)).await;
drop(request);
let (elapsed, kind) = reader.await.unwrap();
assert_eq!(kind, io::ErrorKind::TimedOut);
assert!(elapsed >= Duration::from_secs(66), "{elapsed:?}");
assert!(elapsed < Duration::from_secs(68), "{elapsed:?}");
}
}