whatsapp_rust/
sync_task.rs1use std::sync::{Arc, Mutex, MutexGuard};
2
3use wacore::appstate::patch_decode::WAPatchName;
4use wacore::messages::DetachedHistorySyncNotification;
5
6pub(crate) struct HistorySyncActivity {
10 state: Mutex<HistorySyncActivityState>,
11 idle_notifier: event_listener::Event,
12}
13
14#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
15struct HistorySyncGeneration(usize);
16
17#[derive(Debug, Default)]
18struct HistorySyncActivityState {
19 generation: HistorySyncGeneration,
20 tasks: usize,
21 tasks_peak: usize,
22 payload_bytes: usize,
23 payload_bytes_peak: usize,
24}
25
26#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
27pub(crate) struct HistorySyncActivitySnapshot {
28 pub(crate) tasks: usize,
29 pub(crate) tasks_peak: usize,
30 pub(crate) payload_bytes: usize,
31 pub(crate) payload_bytes_peak: usize,
32}
33
34impl HistorySyncActivity {
35 pub(crate) fn new() -> Self {
36 Self {
37 state: Mutex::new(HistorySyncActivityState::default()),
38 idle_notifier: event_listener::Event::new(),
39 }
40 }
41
42 pub(crate) fn begin(self: &Arc<Self>, payload_bytes: usize) -> HistorySyncTaskTracker {
43 let mut state = self.state_guard();
44 let generation = state.generation;
45 state.tasks = state.tasks.saturating_add(1);
46 state.tasks_peak = state.tasks_peak.max(state.tasks);
47 state.payload_bytes = state.payload_bytes.saturating_add(payload_bytes);
48 state.payload_bytes_peak = state.payload_bytes_peak.max(state.payload_bytes);
49 drop(state);
50 HistorySyncTaskTracker {
51 activity: Arc::clone(self),
52 generation,
53 payload_bytes,
54 }
55 }
56
57 pub(crate) fn reset(&self) {
58 let mut state = self.state_guard();
59 state.generation.0 = state.generation.0.wrapping_add(1);
60 state.tasks = 0;
61 state.payload_bytes = 0;
62 drop(state);
63 self.idle_notifier.notify(usize::MAX);
64 }
65
66 pub(crate) fn listen(&self) -> event_listener::EventListener {
67 self.idle_notifier.listen()
68 }
69
70 pub(crate) fn tasks(&self) -> usize {
71 self.state_guard().tasks
72 }
73
74 pub(crate) fn snapshot(&self) -> HistorySyncActivitySnapshot {
75 let state = self.state_guard();
76 HistorySyncActivitySnapshot {
77 tasks: state.tasks,
78 tasks_peak: state.tasks_peak,
79 payload_bytes: state.payload_bytes,
80 payload_bytes_peak: state.payload_bytes_peak,
81 }
82 }
83
84 fn release(&self, generation: HistorySyncGeneration, payload_bytes: usize) {
85 let mut state = self.state_guard();
86 if state.generation != generation {
87 return;
88 }
89 state.payload_bytes = state.payload_bytes.saturating_sub(payload_bytes);
90 state.tasks = state.tasks.saturating_sub(1);
91 let is_idle = state.tasks == 0;
92 drop(state);
93 if is_idle {
94 self.idle_notifier.notify(usize::MAX);
95 }
96 }
97
98 fn state_guard(&self) -> MutexGuard<'_, HistorySyncActivityState> {
99 self.state
100 .lock()
101 .unwrap_or_else(std::sync::PoisonError::into_inner)
102 }
103}
104
105#[doc(hidden)]
106pub struct HistorySyncTaskTracker {
107 activity: Arc<HistorySyncActivity>,
108 generation: HistorySyncGeneration,
109 payload_bytes: usize,
110}
111
112impl HistorySyncTaskTracker {
113 pub(crate) fn set_payload_bytes(&mut self, payload_bytes: usize) {
114 let mut state = self.activity.state_guard();
115 if state.generation != self.generation {
116 self.payload_bytes = payload_bytes;
117 return;
118 }
119 match payload_bytes.cmp(&self.payload_bytes) {
120 std::cmp::Ordering::Greater => {
121 let additional = payload_bytes - self.payload_bytes;
122 state.payload_bytes = state.payload_bytes.saturating_add(additional);
123 state.payload_bytes_peak = state.payload_bytes_peak.max(state.payload_bytes);
124 }
125 std::cmp::Ordering::Less => {
126 let released = self.payload_bytes - payload_bytes;
127 state.payload_bytes = state.payload_bytes.saturating_sub(released);
128 }
129 std::cmp::Ordering::Equal => {}
130 }
131 self.payload_bytes = payload_bytes;
132 }
133}
134
135impl std::fmt::Debug for HistorySyncTaskTracker {
136 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
137 f.debug_struct("HistorySyncTaskTracker")
138 .field("generation", &self.generation)
139 .field("payload_bytes", &self.payload_bytes)
140 .finish_non_exhaustive()
141 }
142}
143
144impl Drop for HistorySyncTaskTracker {
145 fn drop(&mut self) {
146 self.activity.release(self.generation, self.payload_bytes);
147 }
148}
149
150#[derive(Debug)]
151pub enum MajorSyncTask {
152 HistorySync {
153 message_id: String,
154 notification: Box<DetachedHistorySyncNotification>,
155 tracker: HistorySyncTaskTracker,
156 },
157 AppStateSync {
158 name: WAPatchName,
159 full_sync: bool,
160 },
161}
162
163#[cfg(test)]
164mod tests {
165 use super::*;
166
167 #[test]
168 fn history_sync_tracker_updates_payload_peaks_and_releases_exactly_once() {
169 let activity = Arc::new(HistorySyncActivity::new());
170 let mut first = activity.begin(100);
171 let second = activity.begin(200);
172
173 first.set_payload_bytes(150);
174 assert_eq!(
175 activity.snapshot(),
176 HistorySyncActivitySnapshot {
177 tasks: 2,
178 tasks_peak: 2,
179 payload_bytes: 350,
180 payload_bytes_peak: 350,
181 }
182 );
183
184 drop(first);
185 assert_eq!(activity.tasks(), 1);
186 assert_eq!(activity.snapshot().payload_bytes, 200);
187
188 drop(second);
189 assert_eq!(
190 activity.snapshot(),
191 HistorySyncActivitySnapshot {
192 tasks: 0,
193 tasks_peak: 2,
194 payload_bytes: 0,
195 payload_bytes_peak: 350,
196 }
197 );
198 }
199
200 #[test]
201 fn queued_history_sync_tracker_cannot_release_current_generation() {
202 let activity = Arc::new(HistorySyncActivity::new());
203 let mut stale_tracker = activity.begin(1024);
204 activity.reset();
205 let current_tracker = activity.begin(2048);
206
207 stale_tracker.set_payload_bytes(4096);
208 drop(stale_tracker);
209
210 assert_eq!(activity.tasks(), 1);
211 assert_eq!(activity.snapshot().payload_bytes, 2048);
212
213 drop(current_tracker);
214 assert_eq!(activity.tasks(), 0);
215 assert_eq!(activity.snapshot().payload_bytes, 0);
216 }
217}