cranpose_services/
background.rs1use std::sync::{
11 Arc, Mutex, OnceLock,
12 atomic::{AtomicUsize, Ordering},
13};
14
15pub trait BackgroundActivity: Send + Sync {
19 fn set_active(&self, active: bool);
22}
23
24pub type BackgroundActivityRef = Arc<dyn BackgroundActivity>;
25
26fn slot() -> &'static Mutex<Option<BackgroundActivityRef>> {
27 static SLOT: OnceLock<Mutex<Option<BackgroundActivityRef>>> = OnceLock::new();
28 SLOT.get_or_init(|| Mutex::new(None))
29}
30
31pub fn set_platform_background_activity(activity: BackgroundActivityRef) {
33 if background_active() {
34 activity.set_active(true);
35 }
36 if let Ok(mut s) = slot().lock() {
37 *s = Some(activity);
38 }
39}
40
41pub fn clear_platform_background_activity() {
43 if let Ok(mut s) = slot().lock() {
44 *s = None;
45 }
46}
47
48pub fn background_activity() -> Option<BackgroundActivityRef> {
50 slot().lock().ok().and_then(|s| s.clone())
51}
52
53static ACTIVE_LEASES: AtomicUsize = AtomicUsize::new(0);
54
55pub struct BackgroundWorkLease {
58 active: bool,
59}
60
61impl Drop for BackgroundWorkLease {
62 fn drop(&mut self) {
63 if !self.active {
64 return;
65 }
66 self.active = false;
67 if ACTIVE_LEASES.fetch_sub(1, Ordering::AcqRel) == 1
68 && let Some(activity) = background_activity()
69 {
70 activity.set_active(false);
71 }
72 }
73}
74
75pub fn acquire_background_work() -> BackgroundWorkLease {
77 if ACTIVE_LEASES.fetch_add(1, Ordering::AcqRel) == 0
78 && let Some(activity) = background_activity()
79 {
80 activity.set_active(true);
81 }
82 BackgroundWorkLease { active: true }
83}
84
85pub fn background_active() -> bool {
94 ACTIVE_LEASES.load(Ordering::Acquire) != 0
95}
96
97#[cfg(test)]
98mod tests {
99 use std::sync::atomic::{AtomicBool, Ordering};
100
101 use super::*;
102
103 #[test]
104 fn registration_round_trips() {
105 let _services = crate::registry::test_service_guard();
106 clear_platform_background_activity();
107 struct Rec(AtomicBool);
108 impl BackgroundActivity for Rec {
109 fn set_active(&self, active: bool) {
110 self.0.store(active, Ordering::SeqCst);
111 }
112 }
113 let rec = Arc::new(Rec(AtomicBool::new(false)));
114 set_platform_background_activity(rec.clone());
115 let first = acquire_background_work();
116 assert!(rec.0.load(Ordering::SeqCst));
117 assert!(background_active());
118 let second = acquire_background_work();
119 drop(first);
120 assert!(background_active());
121 assert!(rec.0.load(Ordering::SeqCst));
122 drop(second);
123 assert!(!background_active());
124 assert!(!rec.0.load(Ordering::SeqCst));
125 clear_platform_background_activity();
126 }
127}