conmonrs/streaming_server/
idle.rs1use crate::mutex::lock;
4use std::{
5 future::{self, Future},
6 sync::{Arc, Mutex},
7};
8use tokio::{
9 select, task,
10 time::{self, Duration, Instant},
11};
12use tokio_util::sync::CancellationToken;
13
14pub(super) const STALL_TIMEOUT: Duration = if cfg!(test) {
23 Duration::from_secs(5)
24} else {
25 Duration::from_secs(60)
26};
27
28#[derive(Clone, Debug)]
32pub(super) struct Activity(Arc<Mutex<Instant>>);
33
34impl Activity {
35 pub(super) fn new() -> Self {
37 Self(Arc::new(Mutex::new(Instant::now())))
38 }
39
40 pub(super) fn touch(&self) {
42 *lock(&self.0) = Instant::now();
43 }
44
45 pub(super) fn last(&self) -> Instant {
47 *lock(&self.0)
48 }
49
50 pub(super) async fn idle(&self, timeout: Option<Duration>) {
53 let Some(timeout) = timeout else {
54 return future::pending().await;
55 };
56 loop {
57 let Some(deadline) = self.last().checked_add(timeout) else {
58 return future::pending().await;
59 };
60 if Instant::now() >= deadline {
61 return;
62 }
63 time::sleep_until(deadline).await;
64 }
65 }
66
67 pub(super) async fn until_idle<T>(
71 &self,
72 timeout: Option<Duration>,
73 run: impl Future<Output = T>,
74 on_idle: impl FnOnce(),
75 ) -> Option<T> {
76 let idle = async {
77 self.idle(timeout).await;
78 on_idle();
79 };
80 select! {
81 output = run => Some(output),
82 () = idle => None,
83 }
84 }
85
86 pub(super) async fn until_stalled<T>(
92 &self,
93 start: Instant,
94 timeout: Duration,
95 run: impl Future<Output = T>,
96 ) -> Option<T> {
97 self.until_stalled_or_idle(start, timeout, None, run).await
98 }
99
100 pub(super) async fn until_stalled_or_idle<T>(
106 &self,
107 start: Instant,
108 timeout: Duration,
109 idle_timeout: Option<Duration>,
110 run: impl Future<Output = T>,
111 ) -> Option<T> {
112 let deadline = |last: Instant| {
113 let stalled = last.max(start).checked_add(timeout)?;
114 let idle = idle_timeout.and_then(|idle_timeout| last.checked_add(idle_timeout));
115 Some(idle.map_or(stalled, |idle| stalled.max(idle)))
116 };
117 let stalled = async {
118 loop {
119 let Some(deadline) = deadline(self.last()) else {
120 return future::pending().await;
121 };
122 if Instant::now() >= deadline {
123 return;
124 }
125 time::sleep_until(deadline).await;
126 }
127 };
128 select! {
129 biased;
130 output = run => Some(output),
131 () = stalled => None,
132 }
133 }
134
135 pub(super) fn watch(
139 &self,
140 timeout: Option<Duration>,
141 ended: CancellationToken,
142 on_idle: impl FnOnce() + Send + 'static,
143 ) {
144 let activity = self.clone();
145 task::spawn(async move {
146 if activity
147 .until_idle(timeout, ended.cancelled(), || {})
148 .await
149 .is_none()
150 {
151 on_idle();
152 }
153 });
154 }
155}
156
157#[cfg(test)]
158mod tests {
159 use super::*;
160
161 #[tokio::test(start_paused = true)]
162 async fn idle_after_the_timeout() {
163 let activity = Activity::new();
164 let timeout = Some(Duration::from_secs(60));
165 time::sleep(Duration::from_secs(50)).await;
166 activity.touch();
167 assert!(
168 time::timeout(Duration::from_secs(59), activity.idle(timeout))
169 .await
170 .is_err()
171 );
172 time::timeout(Duration::from_secs(2), activity.idle(timeout))
173 .await
174 .expect("idle");
175 }
176
177 #[tokio::test(start_paused = true)]
178 async fn runs_end_with_the_idle_timeout() {
179 let activity = Activity::new();
180 let timeout = Some(Duration::from_secs(60));
181 let done = activity
182 .until_idle(timeout, time::sleep(Duration::from_secs(30)), || {
183 panic!("idle")
184 })
185 .await;
186 assert_eq!(done, Some(()));
187
188 let idle = Arc::new(Mutex::new(false));
190 struct Check(Arc<Mutex<bool>>);
191 impl Drop for Check {
192 fn drop(&mut self) {
193 assert!(*self.0.lock().unwrap());
194 }
195 }
196 let check = Check(idle.clone());
197 let run = async move {
198 let _check = check;
199 future::pending::<()>().await;
200 };
201 let start = Instant::now();
202 let result = activity
203 .until_idle(timeout, run, || *idle.lock().unwrap() = true)
204 .await;
205 assert_eq!(result, None);
206 assert_eq!(start.elapsed(), Duration::from_secs(30));
207 }
208
209 #[tokio::test(start_paused = true)]
210 async fn only_stalls_end_runs() {
211 let activity = Activity::new();
212 let timeout = Duration::from_secs(5);
213 time::sleep(Duration::from_secs(60)).await;
216 let start = Instant::now();
217 let progress = async {
218 for _ in 0..10 {
219 time::sleep(Duration::from_secs(4)).await;
220 activity.touch();
221 }
222 };
223 assert_eq!(
224 activity.until_stalled(start, timeout, progress).await,
225 Some(())
226 );
227 assert_eq!(start.elapsed(), Duration::from_secs(40));
228
229 let start = Instant::now();
231 let stalls = async {
232 time::sleep(Duration::from_secs(3)).await;
233 activity.touch();
234 future::pending::<()>().await;
235 };
236 assert_eq!(activity.until_stalled(start, timeout, stalls).await, None);
237 assert_eq!(start.elapsed(), Duration::from_secs(8));
238 }
239
240 #[tokio::test(start_paused = true)]
241 async fn stalls_end_runs_after_the_idle_timeout() {
242 let activity = Activity::new();
243 let timeout = Duration::from_secs(5);
244 let idle_timeout = Some(Duration::from_secs(60));
245
246 time::sleep(Duration::from_secs(20)).await;
249 let start = Instant::now();
250 let stalls = future::pending::<()>();
251 assert_eq!(
252 activity
253 .until_stalled_or_idle(start, timeout, idle_timeout, stalls)
254 .await,
255 None
256 );
257 assert_eq!(start.elapsed(), Duration::from_secs(40));
258
259 let start = Instant::now();
261 let stalls = future::pending::<()>();
262 assert_eq!(
263 activity
264 .until_stalled_or_idle(start, timeout, idle_timeout, stalls)
265 .await,
266 None
267 );
268 assert_eq!(start.elapsed(), timeout);
269
270 activity.touch();
272 let start = Instant::now();
273 let progress = async {
274 for _ in 0..3 {
275 time::sleep(Duration::from_secs(50)).await;
276 activity.touch();
277 }
278 };
279 assert_eq!(
280 activity
281 .until_stalled_or_idle(start, timeout, idle_timeout, progress)
282 .await,
283 Some(())
284 );
285 assert_eq!(start.elapsed(), Duration::from_secs(150));
286 }
287
288 #[tokio::test(start_paused = true)]
289 async fn huge_idle_timeouts_leave_the_stall_timeout() {
290 let activity = Activity::new();
291 let timeout = Duration::from_secs(5);
292 let start = Instant::now();
293 let stalls = future::pending::<()>();
294 let ended = activity.until_stalled_or_idle(start, timeout, Some(Duration::MAX), stalls);
295 assert_eq!(time::timeout(10 * timeout, ended).await, Ok(None));
296 assert_eq!(start.elapsed(), timeout);
297 }
298
299 #[tokio::test(start_paused = true)]
300 async fn huge_timeouts_are_none() {
301 let activity = Activity::new();
302 for timeout in [
303 None,
304 Some(Duration::MAX),
305 Some(Duration::from_secs(u64::MAX)),
306 ] {
307 assert!(
308 time::timeout(Duration::from_secs(3600), activity.idle(timeout))
309 .await
310 .is_err()
311 );
312 }
313 }
314}