1use serde::{Deserialize, Serialize};
3use std::collections::BTreeMap;
4use std::sync::{Arc, Mutex, Weak};
5
6#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
7#[serde(rename_all = "camelCase")]
8pub struct SyncProgress {
9 pub attempt: u64,
10 pub state: ProgressState,
11 pub phase: ProgressPhase,
12 #[serde(skip_serializing_if = "Option::is_none")]
13 pub subscription_id: Option<String>,
14 #[serde(skip_serializing_if = "Option::is_none")]
15 pub table: Option<String>,
16 #[serde(skip_serializing_if = "Option::is_none")]
17 pub segment_id: Option<String>,
18 pub bytes_received: u64,
19 #[serde(skip_serializing_if = "Option::is_none")]
20 pub bytes_total: Option<u64>,
21 pub rows_processed: u64,
22 #[serde(skip_serializing_if = "Option::is_none")]
23 pub rows_total: Option<u64>,
24 #[serde(skip_serializing_if = "Option::is_none")]
25 pub error_code: Option<String>,
26 #[serde(skip_serializing_if = "Option::is_none")]
28 pub retry_delay_ms: Option<u64>,
29}
30#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq)]
31#[serde(rename_all = "camelCase")]
32pub enum ProgressState {
33 Running,
34 Complete,
35 Failed,
36}
37#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq)]
38#[serde(rename_all = "camelCase")]
39pub enum ProgressPhase {
40 Request,
41 Download,
42 Import,
43}
44
45pub type SyncProgressListener = Arc<dyn Fn(&SyncProgress) + Send + Sync>;
46
47struct Delivery {
48 callback: SyncProgressListener,
49 revision: Mutex<u64>,
50}
51impl Delivery {
52 fn notify(&self, revision: u64, snapshot: &SyncProgress) {
53 let mut delivered = self.revision.lock().expect("progress delivery lock");
54 if revision <= *delivered {
55 return;
56 }
57 *delivered = revision;
58 let _ =
61 std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| (self.callback)(snapshot)));
62 }
63}
64#[derive(Default)]
65struct Inner {
66 snapshot: Option<SyncProgress>,
67 listeners: BTreeMap<u64, Arc<Delivery>>,
68 revision: u64,
69 next_listener: u64,
70}
71
72#[derive(Clone, Default)]
73pub struct ProgressObserver(Arc<Mutex<Inner>>);
74
75pub struct ProgressSubscription {
77 inner: Weak<Mutex<Inner>>,
78 id: u64,
79}
80impl Drop for ProgressSubscription {
81 fn drop(&mut self) {
82 if let Some(inner) = self.inner.upgrade() {
83 inner
84 .lock()
85 .expect("progress lock")
86 .listeners
87 .remove(&self.id);
88 }
89 }
90}
91impl ProgressObserver {
92 pub fn snapshot(&self) -> Option<SyncProgress> {
93 self.0.lock().expect("progress lock").snapshot.clone()
94 }
95 pub fn subscribe(
96 &self,
97 listener: impl Fn(&SyncProgress) + Send + Sync + 'static,
98 ) -> ProgressSubscription {
99 let listener = Arc::new(Delivery {
100 callback: Arc::new(listener),
101 revision: Mutex::new(0),
102 });
103 let (id, revision, snapshot) = {
104 let mut inner = self.0.lock().expect("progress lock");
105 inner.next_listener += 1;
106 let id = inner.next_listener;
107 inner.listeners.insert(id, listener.clone());
108 (id, inner.revision, inner.snapshot.clone())
109 };
110 if let Some(snapshot) = snapshot {
111 listener.notify(revision, &snapshot);
112 }
113 ProgressSubscription {
114 inner: Arc::downgrade(&self.0),
115 id,
116 }
117 }
118 pub(crate) fn start(&self) {
119 let attempt = self.snapshot().map_or(1, |s| s.attempt + 1);
120 self.emit(SyncProgress {
121 attempt,
122 state: ProgressState::Running,
123 phase: ProgressPhase::Request,
124 subscription_id: None,
125 table: None,
126 segment_id: None,
127 bytes_received: 0,
128 bytes_total: None,
129 rows_processed: 0,
130 rows_total: None,
131 error_code: None,
132 retry_delay_ms: None,
133 });
134 }
135 pub(crate) fn update(&self, update: impl FnOnce(&mut SyncProgress)) {
136 if let Some(mut snapshot) = self.snapshot() {
137 if snapshot.state != ProgressState::Running {
138 return;
139 }
140 update(&mut snapshot);
141 self.emit(snapshot);
142 }
143 }
144 fn emit(&self, snapshot: SyncProgress) {
145 let (revision, listeners) = {
146 let mut inner = self.0.lock().expect("progress lock");
147 inner.snapshot = Some(snapshot.clone());
148 inner.revision += 1;
149 (
150 inner.revision,
151 inner.listeners.values().cloned().collect::<Vec<_>>(),
152 )
153 };
154 for listener in listeners {
155 listener.notify(revision, &snapshot);
156 }
157 }
158}
159
160#[cfg(test)]
161mod tests {
162 use super::*;
163 #[test]
164 fn observes_running_work_and_unsubscribes_without_affecting_sync() {
165 let observer = ProgressObserver::default();
166 observer.start();
167 let events = Arc::new(Mutex::new(Vec::new()));
168 let captured = events.clone();
169 let reader = observer.clone();
170 let subscription = observer.subscribe(move |progress| {
171 assert!(reader.snapshot().is_some()); captured.lock().unwrap().push(progress.clone());
173 });
174 let _broken = observer.subscribe(|_| panic!("observer failure"));
175 observer.update(|p| {
176 p.phase = ProgressPhase::Import;
177 p.rows_processed = 1024;
178 });
179 assert_eq!(events.lock().unwrap().len(), 2);
180 drop(subscription);
181 observer.update(|p| {
182 p.state = ProgressState::Failed;
183 p.error_code = Some("sync.transport_failed".into());
184 });
185 assert_eq!(events.lock().unwrap().len(), 2);
186 observer.start();
187 let snapshot = observer.snapshot().unwrap();
188 assert_eq!(snapshot.attempt, 2);
189 assert_eq!(snapshot.rows_processed, 0);
190 assert_eq!(snapshot.error_code, None);
191 }
192 #[test]
193 fn late_subscription_replay_cannot_move_progress_backwards() {
194 let observer = ProgressObserver::default();
195 observer.start();
196 let before = observer.snapshot().unwrap();
197 let events = Arc::new(Mutex::new(Vec::new()));
198 let captured = events.clone();
199 let delivery = Delivery {
200 callback: Arc::new(move |p| captured.lock().unwrap().push(p.rows_processed)),
201 revision: Mutex::new(0),
202 };
203 let mut after = before.clone();
204 after.rows_processed = 1024;
205 delivery.notify(2, &after);
206 delivery.notify(1, &before);
207 assert_eq!(*events.lock().unwrap(), vec![1024]);
208 }
209}