Skip to main content

coven_database/
transfer_watch.rs

1//! How far each file in flight one way has got, for a reader that draws each
2//! one: all of them first, then each file whose progress moved, started or
3//! ended, and how it ended (E7, E8). Progress lives in this process's memory
4//! and changes with each step of bytes, so no table holds it and no live
5//! query reads it.
6
7use crate::observation::CommitSubscription;
8use crate::transfers_in_flight::InFlight;
9use crate::TransferProgress;
10use coven_foundation::id_source::FileId;
11use std::collections::BTreeMap;
12use tokio::sync::watch;
13
14/// A change to one file's progress, or every file's.
15#[derive(Clone, Debug, PartialEq, Eq)]
16pub enum TransferChange {
17    /// Every file in flight, as a reader first hears them; a file not
18    /// listed has nothing in flight.
19    Reset(BTreeMap<FileId, TransferProgress>),
20    /// The file started, or its progress moved.
21    Updated {
22        /// The file, as `coven_sending` or `coven_downloading` names it.
23        file: FileId,
24        /// How far it has got now.
25        progress: TransferProgress,
26    },
27    /// The file's transfer ended.
28    Removed {
29        /// The file whose transfer ended.
30        file: FileId,
31        /// How it ended.
32        end: TransferEnd,
33    },
34}
35
36/// How a file's transfer ended, decided in the step that takes it out of
37/// the files in flight. When two attempts move one file at once, the last
38/// to end says it.
39#[derive(Clone, Copy, Debug, PartialEq, Eq)]
40pub enum TransferEnd {
41    /// Its bytes all moved: an upload stored them, or a download assembled
42    /// and checked the file's whole copy, which the write committing its
43    /// pin keeps.
44    Finished,
45    /// It failed, and its failure went where the transfer's failures go:
46    /// an upload's pending record, a pin's call.
47    Failed,
48    /// A hold stopped it before it finished, and nothing failed: a metered
49    /// network, and for an upload also the pause, or a self-replacement
50    /// pausing writes. It starts again once the hold ends (§12.3, §17.8,
51    /// §17.9).
52    Held,
53    /// Whatever moved its bytes dropped it before it ended, as a pin's
54    /// caller does that drops the call.
55    Dropped,
56}
57
58/// What a reader last heard of one file: how far it had got, or, once it
59/// ended since, how.
60#[derive(Clone, Copy, PartialEq, Eq)]
61pub(crate) enum Heard {
62    Moved(TransferProgress),
63    Ended(TransferEnd),
64}
65
66/// What a reader that heard `heard` must hear to hold `now`, the files in
67/// flight: each file that ended, then each file new, moved, or started
68/// again since it ended. `heard` becomes `now`.
69pub(crate) fn changes_since(
70    heard: &mut BTreeMap<FileId, Heard>,
71    now: BTreeMap<FileId, TransferProgress>,
72) -> Vec<TransferChange> {
73    let mut changes: Vec<TransferChange> = heard
74        .iter()
75        .filter_map(|(file, heard)| match heard {
76            Heard::Ended(end) => Some(TransferChange::Removed { file: *file, end: *end }),
77            Heard::Moved(_) => None,
78        })
79        .collect();
80    changes.extend(
81        now.iter()
82            .filter(|(file, progress)| heard.get(*file) != Some(&Heard::Moved(**progress)))
83            .map(|(file, progress)| TransferChange::Updated { file: *file, progress: *progress }),
84    );
85    *heard = now.into_iter().map(|(file, progress)| (file, Heard::Moved(progress))).collect();
86    changes
87}
88
89/// Hears how far each file in flight one way has got. Changes that arrive
90/// faster than the reader reads are heard together: the next read names
91/// each file that differs from what the reader last heard, and how each
92/// one it heard in flight ended.
93pub struct TransferWatch {
94    owner: InFlight,
95    /// This watch's number among its owner's.
96    number: u64,
97    /// Told each time a file starts, moves or ends.
98    changed: watch::Receiver<()>,
99    /// Ends the watch once the store closes.
100    store: CommitSubscription,
101}
102
103impl TransferWatch {
104    pub(crate) fn new(
105        owner: InFlight,
106        number: u64,
107        changed: watch::Receiver<()>,
108        store: CommitSubscription,
109    ) -> Self {
110        Self { owner, number, changed, store }
111    }
112
113    /// Every file in flight as one `Reset` on the first read; after that,
114    /// wait for a file's progress to change and name each file it changed
115    /// for. `None` once the store has closed.
116    pub async fn next(&mut self) -> Option<Vec<TransferChange>> {
117        loop {
118            if self.store.state().closed() {
119                return None;
120            }
121            // Seen before the read, so a change after the read wakes the
122            // wait below.
123            self.changed.borrow_and_update();
124            let changes = self.owner.read(self.number);
125            if !changes.is_empty() {
126                return Some(changes);
127            }
128            tokio::select! {
129                changed = self.changed.changed() => {
130                    changed.expect("the owner this watch holds keeps the sender");
131                }
132                () = self.store.changed() => {}
133            }
134        }
135    }
136}
137
138impl Drop for TransferWatch {
139    fn drop(&mut self) {
140        self.owner.forget(self.number);
141    }
142}
143
144#[cfg(test)]
145#[path = "transfer_watch_tests.rs"]
146mod tests;