1use std::sync::{Arc, Mutex};
4
5#[derive(Clone, Copy, PartialEq, Eq)]
10pub(crate) enum NoticeKind {
11 ClientVersion,
13 GuestDeprecation,
15}
16
17type NoticeHandler = Arc<dyn Fn(&str) + Send + Sync + 'static>;
18
19static HANDLER: Mutex<Option<NoticeHandler>> = Mutex::new(None);
20static SEEN: Mutex<Vec<NoticeKind>> = Mutex::new(Vec::new());
21
22pub fn set_notice_handler(callback: impl Fn(&str) + Send + Sync + 'static) {
26 let _previous = HANDLER.lock().unwrap().replace(Arc::new(callback));
29}
30
31pub fn clear_notice_handler() {
37 let _previous = HANDLER.lock().unwrap().take();
38}
39
40pub(crate) fn notify(kind: NoticeKind, message: &str) {
41 let callback = HANDLER.lock().unwrap().clone();
42 let Some(callback) = callback else {
43 return;
45 };
46 {
47 let mut seen = SEEN.lock().unwrap();
48 if seen.contains(&kind) {
49 return;
50 }
51 seen.push(kind);
52 }
53 callback(message);
54}
55
56#[cfg(test)]
57mod tests {
58 use super::*;
59
60 #[test]
63 fn delivers_each_kind_at_most_once_and_holds_them_while_cleared() {
64 let delivered = Arc::new(Mutex::new(Vec::new()));
65 let sink = Arc::clone(&delivered);
66 let install = move || {
67 let sink = Arc::clone(&sink);
68 set_notice_handler(move |message| sink.lock().unwrap().push(message.to_string()));
69 };
70
71 notify(NoticeKind::GuestDeprecation, "too early");
73
74 install();
75 notify(NoticeKind::GuestDeprecation, "upgrade sb_1");
76 notify(NoticeKind::GuestDeprecation, "upgrade sb_2");
77
78 clear_notice_handler();
80 notify(NoticeKind::ClientVersion, "cli notice while cleared");
81 install();
82 notify(NoticeKind::ClientVersion, "cli notice after reinstall");
83 notify(NoticeKind::ClientVersion, "cli notice repeat");
84
85 assert_eq!(
86 *delivered.lock().unwrap(),
87 vec![
88 "upgrade sb_1".to_string(),
89 "cli notice after reinstall".to_string()
90 ]
91 );
92
93 struct PanicOnDrop;
96 impl Drop for PanicOnDrop {
97 fn drop(&mut self) {
98 panic!("panic in dropped handler state");
99 }
100 }
101 let bomb = PanicOnDrop;
102 set_notice_handler(move |_| {
103 let _ = &bomb;
104 });
105 assert!(
106 std::panic::catch_unwind(|| set_notice_handler(|_| {})).is_err(),
107 "displacing the bomb handler should panic outside the lock"
108 );
109 install();
110 }
111}