1use std::sync::atomic::{AtomicUsize, Ordering};
11use std::sync::{Mutex, PoisonError};
12
13use tokio::time::{Duration, Instant};
14use tokio_util::sync::CancellationToken;
15
16pub const SCRIPT_GIVE_UP: Duration = Duration::from_mins(10);
18
19#[derive(Debug)]
25pub struct ProgressClock {
26 last: Mutex<Instant>,
27 holds: AtomicUsize,
28}
29
30impl ProgressClock {
31 #[must_use]
33 pub fn new() -> Self {
34 Self {
35 last: Mutex::new(Instant::now()),
36 holds: AtomicUsize::new(0),
37 }
38 }
39
40 pub fn tick(&self) {
42 *self.last.lock().unwrap_or_else(PoisonError::into_inner) = Instant::now();
43 }
44
45 #[must_use]
47 pub fn last(&self) -> Instant {
48 if self.holds.load(Ordering::Acquire) > 0 {
49 return Instant::now();
50 }
51 *self.last.lock().unwrap_or_else(PoisonError::into_inner)
52 }
53
54 #[must_use]
57 pub fn hold(&self) -> ClockHold<'_> {
58 self.holds.fetch_add(1, Ordering::AcqRel);
59 ClockHold(self)
60 }
61}
62
63#[derive(Debug)]
65pub struct ClockHold<'a>(&'a ProgressClock);
66
67impl Drop for ClockHold<'_> {
68 fn drop(&mut self) {
69 self.0.tick();
70 self.0.holds.fetch_sub(1, Ordering::AcqRel);
71 }
72}
73
74impl Default for ProgressClock {
75 fn default() -> Self {
76 Self::new()
77 }
78}
79
80#[derive(Debug, Clone)]
88pub struct StopPolicy {
89 pub give_up_after: Option<Duration>,
91 pub clock: std::sync::Arc<ProgressClock>,
93 halt: CancellationToken,
96}
97
98impl StopPolicy {
99 #[must_use]
102 pub fn new(
103 is_terminal: bool,
104 give_up_after: Option<Duration>,
105 clock: std::sync::Arc<ProgressClock>,
106 ) -> Self {
107 let give_up_after = give_up_after.or((!is_terminal).then_some(SCRIPT_GIVE_UP));
108 Self {
109 give_up_after,
110 clock,
111 halt: CancellationToken::new(),
112 }
113 }
114
115 #[must_use]
118 pub(crate) fn next_pass(&self) -> Self {
119 Self {
120 give_up_after: self.give_up_after,
121 clock: std::sync::Arc::clone(&self.clock),
122 halt: CancellationToken::new(),
123 }
124 }
125
126 pub(crate) fn halt(&self) {
128 self.halt.cancel();
129 }
130
131 pub(crate) fn is_halted(&self) -> bool {
133 self.halt.is_cancelled()
134 }
135
136 pub(crate) fn ensure_running(&self) -> Result<(), Halted> {
142 if self.is_halted() {
143 Err(Halted)
144 } else {
145 Ok(())
146 }
147 }
148
149 pub(crate) async fn halted(&self) -> Halted {
151 self.halt.cancelled().await;
152 Halted
153 }
154
155 pub async fn expired(&self) -> GaveUp {
158 let Some(limit) = self.give_up_after else {
159 return std::future::pending().await;
160 };
161 loop {
162 let deadline = self.clock.last() + limit;
163 tokio::time::sleep_until(deadline).await;
164 if self.clock.last() + limit <= Instant::now() {
165 return GaveUp { idle: limit };
166 }
167 }
168 }
169}
170
171#[derive(Debug, Clone, Copy, PartialEq, Eq)]
173pub struct GaveUp {
174 pub idle: Duration,
176}
177
178impl std::fmt::Display for GaveUp {
179 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
180 let secs = self.idle.as_secs();
181 if secs >= 60 && secs.is_multiple_of(60) {
182 write!(f, "no progress for {}m; rerun to resume", secs / 60)
183 } else {
184 write!(f, "no progress for {secs}s; rerun to resume")
185 }
186 }
187}
188
189impl std::error::Error for GaveUp {}
190
191#[derive(Debug, Clone, Copy, PartialEq, Eq)]
195pub(crate) struct Halted;
196
197impl std::fmt::Display for Halted {
198 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
199 f.write_str("another entry ended the pull")
200 }
201}
202
203impl std::error::Error for Halted {}
204
205#[cfg(test)]
206mod tests;