use std::sync::{Arc, Mutex};
#[derive(Clone, Copy, PartialEq, Eq)]
pub(crate) enum NoticeKind {
ClientVersion,
GuestDeprecation,
}
type NoticeHandler = Arc<dyn Fn(&str) + Send + Sync + 'static>;
static HANDLER: Mutex<Option<NoticeHandler>> = Mutex::new(None);
static SEEN: Mutex<Vec<NoticeKind>> = Mutex::new(Vec::new());
pub fn set_notice_handler(callback: impl Fn(&str) + Send + Sync + 'static) {
let _previous = HANDLER.lock().unwrap().replace(Arc::new(callback));
}
pub fn clear_notice_handler() {
let _previous = HANDLER.lock().unwrap().take();
}
pub(crate) fn notify(kind: NoticeKind, message: &str) {
let callback = HANDLER.lock().unwrap().clone();
let Some(callback) = callback else {
return;
};
{
let mut seen = SEEN.lock().unwrap();
if seen.contains(&kind) {
return;
}
seen.push(kind);
}
callback(message);
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn delivers_each_kind_at_most_once_and_holds_them_while_cleared() {
let delivered = Arc::new(Mutex::new(Vec::new()));
let sink = Arc::clone(&delivered);
let install = move || {
let sink = Arc::clone(&sink);
set_notice_handler(move |message| sink.lock().unwrap().push(message.to_string()));
};
notify(NoticeKind::GuestDeprecation, "too early");
install();
notify(NoticeKind::GuestDeprecation, "upgrade sb_1");
notify(NoticeKind::GuestDeprecation, "upgrade sb_2");
clear_notice_handler();
notify(NoticeKind::ClientVersion, "cli notice while cleared");
install();
notify(NoticeKind::ClientVersion, "cli notice after reinstall");
notify(NoticeKind::ClientVersion, "cli notice repeat");
assert_eq!(
*delivered.lock().unwrap(),
vec![
"upgrade sb_1".to_string(),
"cli notice after reinstall".to_string()
]
);
struct PanicOnDrop;
impl Drop for PanicOnDrop {
fn drop(&mut self) {
panic!("panic in dropped handler state");
}
}
let bomb = PanicOnDrop;
set_notice_handler(move |_| {
let _ = &bomb;
});
assert!(
std::panic::catch_unwind(|| set_notice_handler(|_| {})).is_err(),
"displacing the bomb handler should panic outside the lock"
);
install();
}
}