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();
109 clear_platform_background_activity();
110 struct Rec(AtomicBool);
111 impl BackgroundActivity for Rec {
112 fn set_active(&self, active: bool) {
113 self.0.store(active, Ordering::SeqCst);
114 }
115 }
116 let rec = Arc::new(Rec(AtomicBool::new(false)));
117 set_platform_background_activity(rec.clone());
118 let first = acquire_background_work();
119 assert!(rec.0.load(Ordering::SeqCst));
120 assert!(background_active());
121 let second = acquire_background_work();
122 drop(first);
123 assert!(background_active());
124 assert!(rec.0.load(Ordering::SeqCst));
125 drop(second);
126 assert!(!background_active());
127 assert!(!rec.0.load(Ordering::SeqCst));
128 clear_platform_background_activity();
129 }
130}