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