1use std::future::Future;
19use std::io;
20use std::pin::Pin;
21use std::sync::atomic::{AtomicBool, Ordering};
22use std::sync::Arc;
23use std::task::{Context, Poll};
24use std::time::Duration;
25
26use tokio::io::{AsyncRead, AsyncWrite, ReadBuf};
27use tokio::time::{Instant, Sleep};
28
29pub const IDLE_CLOSED: &str = "no bytes moved on this git transfer";
31
32#[derive(Debug, Clone, Default)]
34pub struct IdleFlag(Arc<AtomicBool>);
35
36impl IdleFlag {
37 pub fn timed_out(&self) -> bool {
39 self.0.load(Ordering::SeqCst)
40 }
41}
42
43pub struct Idle<S> {
46 inner: S,
47 timeout: Option<Duration>,
49 last: Instant,
50 timer: Option<Pin<Box<Sleep>>>,
51 flag: IdleFlag,
52}
53
54impl<S> Idle<S> {
55 pub fn new(inner: S, timeout: Duration) -> (Self, IdleFlag) {
57 Self::maybe(inner, Some(timeout))
58 }
59
60 pub fn maybe(inner: S, timeout: Option<Duration>) -> (Self, IdleFlag) {
62 let flag = IdleFlag::default();
63 let now = Instant::now();
64 (
65 Idle {
66 inner,
67 timeout,
68 last: now,
69 timer: timeout.map(|t| Box::pin(tokio::time::sleep_until(now + t))),
70 flag: flag.clone(),
71 },
72 flag,
73 )
74 }
75
76 fn touch(&mut self) {
78 self.last = Instant::now();
79 }
80
81 fn error(&self) -> io::Error {
82 let secs = self.timeout.map(|t| t.as_secs()).unwrap_or_default();
83 io::Error::new(
84 io::ErrorKind::TimedOut,
85 format!("{IDLE_CLOSED} for {secs} s while it waited on the client; it is closed and its transfer slot is free again"),
86 )
87 }
88
89 fn expired(&mut self, cx: &mut Context<'_>) -> Poll<io::Error> {
92 let Some(timeout) = self.timeout else {
93 return Poll::Pending;
94 };
95 let last = self.last;
96 let Some(timer) = self.timer.as_mut() else {
97 return Poll::Pending;
98 };
99 loop {
100 if timer.as_mut().poll(cx).is_pending() {
101 return Poll::Pending;
102 }
103 let deadline = last + timeout;
104 if Instant::now() >= deadline {
105 break;
106 }
107 timer.as_mut().reset(deadline);
109 }
110 self.flag.0.store(true, Ordering::SeqCst);
111 Poll::Ready(self.error())
112 }
113}
114
115impl<S> std::fmt::Debug for Idle<S> {
116 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
117 f.debug_struct("Idle")
118 .field("timeout", &self.timeout)
119 .finish_non_exhaustive()
120 }
121}
122
123impl<S: AsyncRead + Unpin> AsyncRead for Idle<S> {
124 fn poll_read(
125 self: Pin<&mut Self>,
126 cx: &mut Context<'_>,
127 buf: &mut ReadBuf<'_>,
128 ) -> Poll<io::Result<()>> {
129 let me = self.get_mut();
130 if me.flag.timed_out() {
131 return Poll::Ready(Err(me.error()));
132 }
133 let before = buf.filled().len();
134 match Pin::new(&mut me.inner).poll_read(cx, buf) {
135 Poll::Ready(Ok(())) => {
136 if buf.filled().len() > before {
137 me.touch();
138 }
139 Poll::Ready(Ok(()))
140 }
141 Poll::Ready(Err(e)) => Poll::Ready(Err(e)),
142 Poll::Pending => me.expired(cx).map(Err),
143 }
144 }
145}
146
147impl<S: AsyncWrite + Unpin> AsyncWrite for Idle<S> {
148 fn poll_write(
149 self: Pin<&mut Self>,
150 cx: &mut Context<'_>,
151 buf: &[u8],
152 ) -> Poll<io::Result<usize>> {
153 let me = self.get_mut();
154 if me.flag.timed_out() {
155 return Poll::Ready(Err(me.error()));
156 }
157 match Pin::new(&mut me.inner).poll_write(cx, buf) {
158 Poll::Ready(Ok(n)) => {
159 if n > 0 {
160 me.touch();
161 }
162 Poll::Ready(Ok(n))
163 }
164 Poll::Ready(Err(e)) => Poll::Ready(Err(e)),
165 Poll::Pending => me.expired(cx).map(Err),
167 }
168 }
169
170 fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<()>> {
171 let me = self.get_mut();
172 match Pin::new(&mut me.inner).poll_flush(cx) {
173 Poll::Pending => me.expired(cx).map(Err),
174 ready => ready,
175 }
176 }
177
178 fn poll_shutdown(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<()>> {
179 Pin::new(&mut self.get_mut().inner).poll_shutdown(cx)
180 }
181}
182
183#[cfg(test)]
184mod tests {
185 use super::*;
186 use tokio::io::{AsyncReadExt as _, AsyncWriteExt as _};
187
188 const T: Duration = Duration::from_millis(400);
189
190 #[tokio::test]
193 async fn a_read_that_waits_past_the_timeout_fails_by_name() {
194 let (_client, server) = tokio::io::duplex(64);
195 let (mut idle, flag) = Idle::new(server, T);
196 let started = std::time::Instant::now();
197 let mut buf = [0u8; 8];
198 let err = tokio::time::timeout(Duration::from_secs(5), idle.read(&mut buf))
199 .await
200 .expect("the idle read ends")
201 .expect_err("an idle read times out");
202 assert_eq!(err.kind(), io::ErrorKind::TimedOut);
203 assert!(err.to_string().contains(IDLE_CLOSED), "{err}");
204 assert!(flag.timed_out());
205 let waited = started.elapsed();
206 assert!(waited >= T && waited < 4 * T, "{waited:?}");
207 }
208
209 #[tokio::test]
212 async fn bytes_either_way_keep_the_transfer_alive() {
213 let (mut client, server) = tokio::io::duplex(64);
214 let (mut idle, flag) = Idle::new(server, T);
215 let feeder = tokio::spawn(async move {
216 for _ in 0..4 {
217 tokio::time::sleep(T / 2).await;
218 client.write_all(b"x").await.unwrap();
219 }
220 client
221 });
222 let mut buf = [0u8; 1];
223 for _ in 0..4 {
224 idle.read_exact(&mut buf)
225 .await
226 .expect("a live client is not idle");
227 }
228 let mut client = feeder.await.unwrap();
229 tokio::time::sleep(T * 3 / 4).await;
230 idle.write_all(b"y").await.expect("the server writes");
231 let mut got = [0u8; 1];
232 client.read_exact(&mut got).await.unwrap();
233 let read = tokio::time::timeout(T / 2, idle.read(&mut buf)).await;
236 assert!(
237 read.is_err(),
238 "the server's own write was activity: {read:?}"
239 );
240 assert!(!flag.timed_out());
241 }
242}