Skip to main content

coven_foundation/
progress.rs

1//! How far a long operation has come, one step at a time, so a host can draw
2//! a bar for it instead of a bare spinner (api.md E1).
3
4use crate::clock::ClockRef;
5use std::future::Future;
6use std::time::Duration;
7use tokio::sync::watch;
8
9/// How far an operation has come: the step it is on, and how many of that
10/// step's units are done out of its total. Each step names its unit. Steps
11/// arrive in the order the operation runs them, and within a step `done`
12/// only grows and never passes `total`, which never shrinks.
13#[derive(Clone, Copy, Debug, PartialEq, Eq)]
14pub struct Progress<S> {
15    /// The step the operation is on.
16    pub step: S,
17    /// The step's units done so far.
18    pub done: u64,
19    /// The step's units in all, known as the step begins. It grows only
20    /// when work the step had no way to count at its start turns up, such
21    /// as slots that land while it reads them.
22    pub total: u64,
23}
24
25/// The least time between two reports within one step: a host redraws a
26/// few times a second, never once per row.
27pub const REPORT_INTERVAL: Duration = Duration::from_millis(250);
28
29/// Reports one operation's progress into a watch. A step's beginning and its
30/// last unit are always reported; between them, a report waits until
31/// [`REPORT_INTERVAL`] has passed since the one before. Reporting stores the
32/// newest value and writes nothing to a database.
33pub struct ProgressReport<S> {
34    sender: watch::Sender<Option<Progress<S>>>,
35    clock: ClockRef,
36    current: Option<Progress<S>>,
37    /// When the newest report was sent, by the clock's elapsed time.
38    sent: Duration,
39}
40
41impl<S: Clone + PartialEq> ProgressReport<S> {
42    /// Report into `sender`, pacing reports by `clock`.
43    pub fn new(sender: watch::Sender<Option<Progress<S>>>, clock: ClockRef) -> Self {
44        let sent = clock.elapsed();
45        Self { sender, clock, current: None, sent }
46    }
47
48    /// Report one run of the operation `channel` carries, pacing reports
49    /// by `clock`.
50    pub fn of(channel: &ProgressChannel<S>, clock: ClockRef) -> Self {
51        Self::new(channel.0.clone(), clock)
52    }
53
54    /// Report into a watch nobody holds, for work that runs the same steps
55    /// where no host draws them.
56    pub fn unwatched(clock: ClockRef) -> Self {
57        Self::new(watch::channel(None).0, clock)
58    }
59
60    /// Begin `step`, with `total` units to do; reported at once. A step
61    /// begins once, after the steps before it, unless the operation starts
62    /// over.
63    pub fn begin(&mut self, step: S, total: u64) {
64        debug_assert!(self.current.as_ref().is_none_or(|current| current.step != step));
65        self.send(Progress { step, done: 0, total });
66    }
67
68    /// The operation starts over from its first step, as when what it read
69    /// went stale before it could use it. Its steps begin again.
70    pub fn restart(&mut self) {
71        self.current = None;
72    }
73
74    /// `done` of the current step's units are done.
75    pub fn done(&mut self, done: u64) {
76        let Some(current) = self.current.clone() else {
77            debug_assert!(false, "progress is reported within a step");
78            return;
79        };
80        debug_assert!(current.done <= done && done <= current.total);
81        let finished = done == current.total;
82        self.update(Progress { done, ..current }, finished);
83    }
84
85    /// One more of the current step's units is done.
86    pub fn advance(&mut self) {
87        let done = self.current.as_ref().map_or(0, |current| current.done + 1);
88        self.done(done);
89    }
90
91    /// The current step has `total` units in all: more than it counted as
92    /// it began, since work it had no way to count turned up.
93    pub fn grow(&mut self, total: u64) {
94        let Some(current) = self.current.clone() else {
95            debug_assert!(false, "progress is reported within a step");
96            return;
97        };
98        debug_assert!(current.total <= total);
99        self.update(Progress { total, ..current }, false);
100    }
101
102    /// Report `progress`, which another operation's report gave and the
103    /// caller put in this operation's steps, as the part of this operation
104    /// that other one runs: a step it begins is reported at once, and within
105    /// a step its units are paced as [`Self::done`]'s are.
106    pub fn relay(&mut self, progress: Progress<S>) {
107        match &self.current {
108            Some(current) if current.step == progress.step => {
109                let finished = progress.done == progress.total;
110                self.update(progress, finished);
111            }
112            _ => self.send(progress),
113        }
114    }
115
116    /// Report `next` if the step just finished or a report is due; else
117    /// keep it for the next report.
118    fn update(&mut self, next: Progress<S>, finished: bool) {
119        if finished || self.clock.elapsed() >= self.sent + REPORT_INTERVAL {
120            self.send(next);
121        } else {
122            self.current = Some(next);
123        }
124    }
125
126    fn send(&mut self, progress: Progress<S>) {
127        self.current = Some(progress.clone());
128        self.sent = self.clock.elapsed();
129        // Nobody may be watching: the newest value is kept either way.
130        self.sender.send_replace(Some(progress));
131    }
132}
133
134/// Where an owner reports each run of one kind of operation, such as a
135/// store's open, for the host watching it. Each run reports into it afresh.
136#[derive(Clone)]
137pub struct ProgressChannel<S>(watch::Sender<Option<Progress<S>>>);
138
139impl<S: Clone + PartialEq> ProgressChannel<S> {
140    /// A channel no operation has reported into.
141    pub fn new() -> Self {
142        Self(watch::channel(None).0)
143    }
144
145    /// Hear the runs reported from now on.
146    pub fn watch(&self) -> ProgressWatch<S> {
147        ProgressWatch(self.0.subscribe())
148    }
149}
150
151/// Hears the progress an owner's operation reports into its
152/// [`ProgressChannel`], for a host that draws it.
153pub struct ProgressWatch<S>(watch::Receiver<Option<Progress<S>>>);
154
155impl<S: Clone + PartialEq> ProgressWatch<S> {
156    /// Run `work` to its end, calling `report` on this task with each
157    /// progress it reports, as [`forward`] does. The future is `forward`'s
158    /// own, so an opening's future grows by nothing.
159    pub fn forward<T>(
160        self,
161        work: impl Future<Output = T>,
162        report: impl Fn(Progress<S>),
163    ) -> impl Future<Output = T> {
164        forward(self.0, work, report)
165    }
166}
167
168/// Run `work` to its end, calling `report` on this task with the progress
169/// it reports into `progress`. A host that redraws more slowly than `work`
170/// reports sees the newest value, never an older one after it; a report
171/// made just before `work` ends is still delivered, and none twice. What
172/// the watch held before `progress` subscribed belongs to an earlier
173/// operation and is never delivered.
174pub async fn forward<S: Clone + PartialEq, T>(
175    mut progress: watch::Receiver<Option<Progress<S>>>,
176    work: impl Future<Output = T>,
177    report: impl Fn(Progress<S>),
178) -> T {
179    let mut reported = None;
180    let mut deliver = |newest: Option<Progress<S>>| {
181        if newest != reported {
182            if let Some(value) = newest.clone() {
183                reported = newest;
184                report(value);
185            }
186        }
187    };
188    let mut work = std::pin::pin!(work);
189    loop {
190        tokio::select! {
191            biased;
192            output = &mut work => {
193                // Only a value sent since the last one seen is new.
194                let newest = progress.borrow_and_update();
195                let unseen = newest.has_changed().then(|| newest.clone());
196                drop(newest);
197                if let Some(newest) = unseen {
198                    deliver(newest);
199                }
200                return output;
201            }
202            changed = progress.changed() => match changed {
203                Ok(()) => {
204                    let newest = progress.borrow_and_update().clone();
205                    deliver(newest);
206                }
207                // The reporter is gone, and what it reported last was seen
208                // and delivered before the watch said so.
209                Err(_closed) => return work.await,
210            },
211        }
212    }
213}
214
215/// What a hook on a [`RecordingClock`] is called with: each value the
216/// reports send, once, in order.
217#[cfg(feature = "test-utils")]
218type OnRecord<S> = Box<dyn Fn(&Progress<S>) + Send + Sync>;
219
220/// A clock that keeps every value reported into one watch, for a test that
221/// checks steps a host reading the watch could miss. A report reads its
222/// clock just before it sends, so what the watch holds at each reading,
223/// with what it holds at the end, is every value sent, in order. Time and
224/// waits are `clock`'s.
225#[cfg(feature = "test-utils")]
226pub struct RecordingClock<S> {
227    clock: ClockRef,
228    watch: std::sync::Mutex<Option<watch::Receiver<Option<Progress<S>>>>>,
229    seen: std::sync::Mutex<Vec<Progress<S>>>,
230    on_record: std::sync::OnceLock<OnRecord<S>>,
231}
232
233#[cfg(feature = "test-utils")]
234impl<S: Clone + PartialEq> RecordingClock<S> {
235    /// Keep time by `clock`; nothing is recorded until [`Self::watch`].
236    pub fn new(clock: ClockRef) -> Self {
237        Self {
238            clock,
239            watch: std::sync::Mutex::new(None),
240            seen: std::sync::Mutex::new(Vec::new()),
241            on_record: std::sync::OnceLock::new(),
242        }
243    }
244
245    /// Record the values reported into `watch`, from now on, in place of
246    /// the watch an earlier part of the operation reported into, whose
247    /// newest value is recorded first: a join reports the scan that finds
248    /// its keys through its own owner, then the loading of the store it
249    /// publishes through the store's.
250    pub fn watch(&self, watch: watch::Receiver<Option<Progress<S>>>) {
251        self.record();
252        *self.watch.lock().unwrap() = Some(watch);
253    }
254
255    /// Record the values an owner's operation reports into `progress`,
256    /// from now on.
257    pub fn watch_progress(&self, progress: ProgressWatch<S>) {
258        self.watch(progress.0);
259    }
260
261    /// Call `hook` with each value as it is recorded, on the thread that
262    /// read the clock: when that is the reporting work's, the work waits
263    /// until `hook` returns.
264    pub fn on_record(&self, hook: impl Fn(&Progress<S>) + Send + Sync + 'static) {
265        assert!(self.on_record.set(Box::new(hook)).is_ok(), "a recording clock has one hook");
266    }
267
268    /// Every value reported so far, in order.
269    pub fn seen(&self) -> Vec<Progress<S>> {
270        self.record();
271        self.seen.lock().unwrap().clone()
272    }
273
274    fn record(&self) {
275        let recorded = {
276            // Held while the watch is read, so two readers record in the
277            // order the values were sent.
278            let mut seen = self.seen.lock().unwrap();
279            let Some(newest) =
280                self.watch.lock().unwrap().as_ref().map(|watch| watch.borrow().clone())
281            else {
282                return;
283            };
284            match newest.filter(|newest| seen.last() != Some(newest)) {
285                Some(newest) => {
286                    seen.push(newest.clone());
287                    newest
288                }
289                None => return,
290            }
291        };
292        if let Some(hook) = self.on_record.get() {
293            hook(&recorded);
294        }
295    }
296}
297
298#[cfg(feature = "test-utils")]
299impl<S: Clone + PartialEq + Send + Sync> crate::clock::Clock for RecordingClock<S> {
300    fn now(&self) -> std::time::SystemTime {
301        self.clock.now()
302    }
303    fn elapsed(&self) -> Duration {
304        self.record();
305        self.clock.elapsed()
306    }
307    fn deadline(&self, duration: Duration) -> std::pin::Pin<Box<dyn Future<Output = ()> + Send>> {
308        self.clock.deadline(duration)
309    }
310    fn wait_out(&self, wait: Duration) -> std::pin::Pin<Box<dyn Future<Output = ()> + Send>> {
311        self.clock.wait_out(wait)
312    }
313    fn sleep_until(
314        &self,
315        at: std::time::SystemTime,
316    ) -> std::pin::Pin<Box<dyn Future<Output = Result<(), crate::clock::WallTimerError>> + Send>>
317    {
318        self.clock.sleep_until(at)
319    }
320}
321
322#[cfg(test)]
323#[path = "progress_tests.rs"]
324mod tests;