1use super::error::StoreError;
2use crate::store::Device;
3use crate::store::traits::Backend;
4use async_lock::RwLock;
5use event_listener::Event;
6use futures::FutureExt;
7use log::{debug, error};
8use std::sync::Arc;
9use std::sync::atomic::{AtomicBool, Ordering};
10use std::time::Duration;
11use wacore::runtime::{AbortHandle, Runtime, ShutdownSignal, wait_for_shutdown};
12
13pub struct PersistenceManager {
14 device: Arc<RwLock<Device>>,
15 device_snapshot: std::sync::RwLock<Arc<Device>>,
20 backend: Arc<dyn Backend>,
21 dirty: Arc<AtomicBool>,
22 save_notify: Arc<Event>,
23 saver_halted: Arc<AtomicBool>,
25 #[cfg(test)]
26 fail_sender_key_device_clears: AtomicBool,
27 #[cfg(test)]
28 fail_sender_key_device_status_writes: AtomicBool,
29}
30
31impl PersistenceManager {
32 pub async fn new(backend: Arc<dyn Backend>) -> Result<Self, StoreError> {
37 debug!("PersistenceManager: Ensuring device row exists.");
38 let exists = backend.exists().await?;
40 if !exists {
41 debug!("PersistenceManager: No device row found. Creating new device row.");
42 let id = backend.create().await?;
43 debug!("PersistenceManager: Created device row with id={id}.");
44 }
45
46 debug!("PersistenceManager: Attempting to load device data via Backend.");
47 let device_data_opt = backend.load().await?;
48
49 let device = if let Some(serializable_device) = device_data_opt {
50 debug!(
51 "PersistenceManager: Loaded existing device data (PushName: '{}'). Initializing Device.",
52 serializable_device.push_name
53 );
54 let mut dev = Device::new(backend.clone());
55 dev.load_from_serializable(serializable_device);
56 dev
57 } else {
58 debug!("PersistenceManager: No data yet; initializing default Device in memory.");
59 Device::new(backend.clone())
60 };
61
62 let snapshot = Arc::new(device.clone());
63 Ok(Self {
64 device: Arc::new(RwLock::new(device)),
65 device_snapshot: std::sync::RwLock::new(snapshot),
66 backend,
67 dirty: Arc::new(AtomicBool::new(false)),
68 save_notify: Arc::new(Event::new()),
69 saver_halted: Arc::new(AtomicBool::new(false)),
70 #[cfg(test)]
71 fail_sender_key_device_clears: AtomicBool::new(false),
72 #[cfg(test)]
73 fail_sender_key_device_status_writes: AtomicBool::new(false),
74 })
75 }
76
77 pub async fn get_device_arc(&self) -> Arc<RwLock<Device>> {
80 self.device.clone()
81 }
82
83 pub fn get_device_snapshot(&self) -> Arc<Device> {
87 self.device_snapshot
88 .read()
89 .unwrap_or_else(|p| p.into_inner())
90 .clone()
91 }
92
93 pub fn backend(&self) -> Arc<dyn Backend> {
94 self.backend.clone()
95 }
96
97 pub fn is_saver_halted(&self) -> bool {
99 self.saver_halted.load(Ordering::Acquire)
100 }
101
102 pub async fn modify_device<F, R>(&self, modifier: F) -> R
103 where
104 F: FnOnce(&mut Device) -> R,
105 {
106 let mut device_guard = self.device.write().await;
107 let result = modifier(&mut device_guard);
108
109 self.dirty.store(true, Ordering::Relaxed);
113
114 *self
117 .device_snapshot
118 .write()
119 .unwrap_or_else(|p| p.into_inner()) = Arc::new(device_guard.clone());
120 drop(device_guard);
121
122 self.save_notify.notify(1);
123
124 result
125 }
126
127 pub async fn flush(&self) -> Result<(), StoreError> {
129 self.save_to_disk().await
130 }
131
132 async fn save_to_disk(&self) -> Result<(), StoreError> {
133 if self.dirty.swap(false, Ordering::AcqRel) {
134 debug!("Device state is dirty, saving to disk.");
135 let device_guard = self.device.read().await;
136 let serializable_device = device_guard.to_serializable();
137 drop(device_guard);
138
139 if let Err(e) = self.backend.save(&serializable_device).await {
140 self.dirty.store(true, Ordering::Release);
142 return Err(e);
143 }
144 debug!("Device state saved successfully.");
145 }
146 Ok(())
147 }
148
149 pub async fn create_snapshot(
152 &self,
153 name: &str,
154 extra_content: Option<&[u8]>,
155 ) -> Result<(), StoreError> {
156 #[cfg(feature = "debug-snapshots")]
157 {
158 self.save_to_disk().await?;
160 self.backend.snapshot_db(name, extra_content).await
161 }
162 #[cfg(not(feature = "debug-snapshots"))]
163 {
164 let _ = name;
165 let _ = extra_content;
166 log::warn!("Snapshot requested but 'debug-snapshots' feature is disabled");
167 Ok(())
168 }
169 }
170
171 pub fn run_background_saver(
181 self: Arc<Self>,
182 runtime: Arc<dyn Runtime>,
183 interval: Duration,
184 shutdown: ShutdownSignal,
185 ) -> AbortHandle {
186 const MAX_CONSECUTIVE_FAILURES: u32 = 10;
187
188 let rt = runtime.clone();
189 let weak = Arc::downgrade(&self);
190 drop(self);
191 debug!("Background saver started (interval {interval:?})");
192 runtime.spawn(Box::pin(async move {
193 let mut consecutive_failures: u32 = 0;
194
195 if let Some(this) = weak.upgrade()
200 && let Err(e) = this.save_to_disk().await
201 {
202 error!("Background saver: initial flush failed: {e}");
203 consecutive_failures = 1;
204 }
205
206 loop {
207 let Some(this) = weak.upgrade() else {
208 debug!("PersistenceManager dropped, exiting background saver.");
209 return;
210 };
211 let save_listener = this.save_notify.listen();
212 drop(this);
213
214 let should_exit = futures::select! {
215 _ = save_listener.fuse() => false,
216 _ = rt.sleep(interval).fuse() => false,
217 _ = wait_for_shutdown(&shutdown).fuse() => true,
218 };
219
220 let Some(this) = weak.upgrade() else {
221 debug!("PersistenceManager dropped, exiting background saver.");
222 return;
223 };
224 let flush_result = this.save_to_disk().await;
225
226 if should_exit {
229 match &flush_result {
230 Err(e) => {
231 error!("Background saver: final flush on shutdown failed: {e}");
232 }
233 Ok(()) => {
234 debug!("Background saver received shutdown; final flush complete.");
235 }
236 }
237 return;
238 }
239
240 if let Err(e) = flush_result {
241 consecutive_failures += 1;
242 if consecutive_failures >= MAX_CONSECUTIVE_FAILURES {
243 this.saver_halted.store(true, Ordering::Release);
244 error!(
245 "Background saver: {consecutive_failures} consecutive flush failures, \
246 halting to prevent silent data loss. Last error: {e}"
247 );
248 return;
249 }
250 error!(
251 "Background saver flush failed ({consecutive_failures}/{MAX_CONSECUTIVE_FAILURES}): {e}"
252 );
253 } else {
254 consecutive_failures = 0;
255 }
256 }
257 }))
258 }
259}
260
261use super::commands::{DeviceCommand, apply_command_to_device};
262
263impl PersistenceManager {
264 pub async fn process_command(&self, command: DeviceCommand) {
265 self.modify_device(|device| {
266 apply_command_to_device(device, command);
267 })
268 .await;
269 }
270}
271
272impl PersistenceManager {
273 pub async fn get_sender_key_devices(
274 &self,
275 group_jid: &str,
276 ) -> Result<Vec<(String, bool)>, StoreError> {
277 self.backend.get_sender_key_devices(group_jid).await
278 }
279
280 pub async fn set_sender_key_status(
281 &self,
282 group_jid: &str,
283 entries: &[(&str, bool)],
284 ) -> Result<(), StoreError> {
285 #[cfg(test)]
286 if self
287 .fail_sender_key_device_status_writes
288 .load(Ordering::Acquire)
289 {
290 return Err(StoreError::Io(std::io::Error::other(
291 "injected sender-key tracker status-write failure",
292 )));
293 }
294 self.backend.set_sender_key_status(group_jid, entries).await
295 }
296
297 pub async fn clear_sender_key_devices(&self, group_jid: &str) -> Result<(), StoreError> {
298 #[cfg(test)]
299 if self.fail_sender_key_device_clears.load(Ordering::Acquire) {
300 return Err(StoreError::Io(std::io::Error::other(
301 "injected sender-key tracker clear failure",
302 )));
303 }
304 self.backend.clear_sender_key_devices(group_jid).await
305 }
306
307 #[cfg(test)]
308 pub(crate) fn fail_sender_key_device_clears_for_tests(&self, fail: bool) {
309 self.fail_sender_key_device_clears
310 .store(fail, Ordering::Release);
311 }
312
313 #[cfg(test)]
314 pub(crate) fn fail_sender_key_device_status_writes_for_tests(&self, fail: bool) {
315 self.fail_sender_key_device_status_writes
316 .store(fail, Ordering::Release);
317 }
318
319 pub async fn delete_sender_key_device_rows(
320 &self,
321 device_jids: &[&str],
322 ) -> Result<(), StoreError> {
323 self.backend
324 .delete_sender_key_device_rows(device_jids)
325 .await
326 }
327}
328
329#[cfg(test)]
330mod tests {
331 use super::*;
332 use crate::runtime_impl::TokioRuntime;
333 use wacore::time::Instant;
334
335 #[tokio::test]
338 async fn saver_flushes_and_exits_on_shutdown() {
339 let backend = crate::test_utils::create_test_backend().await;
340 let pm = Arc::new(
341 PersistenceManager::new(backend.clone())
342 .await
343 .expect("pm init"),
344 );
345
346 let notifier = wacore::runtime::ShutdownNotifier::new();
347 let shutdown_signal = notifier.subscribe();
348
349 let runtime: Arc<dyn Runtime> = Arc::new(TokioRuntime);
350 let handle =
352 pm.clone()
353 .run_background_saver(runtime, Duration::from_secs(3600), shutdown_signal);
354
355 tokio::time::sleep(Duration::from_millis(50)).await;
357
358 pm.modify_device(|d| {
359 d.push_name = "shutdown-flush".to_string();
360 })
361 .await;
362
363 notifier.notify();
364
365 let deadline = Instant::now() + Duration::from_secs(2);
366 loop {
367 if let Ok(Some(d)) = backend.load().await
368 && d.push_name == "shutdown-flush"
369 {
370 break;
371 }
372 if Instant::now() > deadline {
373 panic!("final flush did not reach backend after shutdown");
374 }
375 tokio::time::sleep(Duration::from_millis(20)).await;
376 }
377
378 drop(handle);
380 }
381
382 #[tokio::test]
387 async fn saver_exits_when_abort_handle_dropped_without_signal() {
388 let backend = crate::test_utils::create_test_backend().await;
389 let pm = Arc::new(PersistenceManager::new(backend).await.expect("pm init"));
390
391 let runtime: Arc<dyn Runtime> = Arc::new(TokioRuntime);
392 let baseline = Arc::strong_count(&runtime);
393
394 let handle = pm.clone().run_background_saver(
395 Arc::clone(&runtime),
396 Duration::from_secs(3600),
397 ShutdownSignal::never(),
398 );
399
400 tokio::time::sleep(Duration::from_millis(50)).await;
401 assert!(
402 Arc::strong_count(&runtime) > baseline,
403 "running saver should hold a captured runtime Arc"
404 );
405
406 drop(handle);
407
408 let deadline = Instant::now() + Duration::from_secs(1);
409 while Arc::strong_count(&runtime) > baseline {
410 if Instant::now() > deadline {
411 panic!(
412 "saver task did not release the runtime Arc within 1s of AbortHandle drop \
413 (strong_count={}, baseline={})",
414 Arc::strong_count(&runtime),
415 baseline
416 );
417 }
418 tokio::time::sleep(Duration::from_millis(10)).await;
419 }
420 }
421
422 #[tokio::test]
433 async fn abort_handle_in_arc_drops_only_when_last_ref_released() {
434 use std::sync::atomic::{AtomicBool, Ordering};
435
436 struct Owner(std::sync::OnceLock<AbortHandle>);
437
438 let owner = Arc::new(Owner(std::sync::OnceLock::new()));
439
440 let aborted = Arc::new(AtomicBool::new(false));
441 let aborted_clone = Arc::clone(&aborted);
442 owner
443 .0
444 .set(AbortHandle::new(move || {
445 aborted_clone.store(true, Ordering::SeqCst);
446 }))
447 .ok()
448 .expect("first set");
449
450 let owner_clone = Arc::clone(&owner);
451 drop(owner);
452 assert!(
453 !aborted.load(Ordering::SeqCst),
454 "handle must survive while another Arc ref is held"
455 );
456
457 drop(owner_clone);
458 assert!(
459 aborted.load(Ordering::SeqCst),
460 "last Arc drop must release the handle and fire abort"
461 );
462 }
463}