cranpose_services/
background.rs1use std::sync::atomic::{AtomicUsize, Ordering};
11use std::sync::Arc;
12use std::sync::Mutex;
13use std::sync::OnceLock;
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 if let Some(activity) = background_activity() {
69 activity.set_active(false);
70 }
71 }
72 }
73}
74
75pub fn acquire_background_work() -> BackgroundWorkLease {
77 if ACTIVE_LEASES.fetch_add(1, Ordering::AcqRel) == 0 {
78 if let Some(activity) = background_activity() {
79 activity.set_active(true);
80 }
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 super::*;
100 use std::sync::atomic::{AtomicBool, Ordering};
101
102 #[test]
103 fn registration_round_trips() {
104 let _services = crate::registry::test_service_guard();
108 clear_platform_background_activity();
109 struct Rec(AtomicBool);
110 impl BackgroundActivity for Rec {
111 fn set_active(&self, active: bool) {
112 self.0.store(active, Ordering::SeqCst);
113 }
114 }
115 let rec = Arc::new(Rec(AtomicBool::new(false)));
116 set_platform_background_activity(rec.clone());
117 let first = acquire_background_work();
118 assert!(rec.0.load(Ordering::SeqCst));
119 assert!(background_active());
120 let second = acquire_background_work();
121 drop(first);
122 assert!(background_active());
123 assert!(rec.0.load(Ordering::SeqCst));
124 drop(second);
125 assert!(!background_active());
126 assert!(!rec.0.load(Ordering::SeqCst));
127 clear_platform_background_activity();
128 }
129}