fix(m1): notify — unwrap_or_default on poisoned config lock, log poisoned PENDING, add take_pending test

This commit is contained in:
pyr0ball 2026-05-18 18:07:34 -07:00
parent 3b7653d731
commit c0f046bd7c

View file

@ -9,14 +9,16 @@ use crate::patterns::MatchedEvent;
static PENDING: Mutex<Vec<MatchedEvent>> = Mutex::new(vec![]); static PENDING: Mutex<Vec<MatchedEvent>> = Mutex::new(vec![]);
pub fn dispatch<R: Runtime>(app: &AppHandle<R>, event: MatchedEvent) { pub fn dispatch<R: Runtime>(app: &AppHandle<R>, event: MatchedEvent) {
let level = { let level = app
let state = app.state::<AppState>(); .state::<AppState>()
let config = state.config.lock().unwrap(); .config
config.display.notification_level.clone() .lock()
}; .map(|c| c.display.notification_level.clone())
.unwrap_or_default();
if let Ok(mut pending) = PENDING.lock() { match PENDING.lock() {
pending.push(event.clone()); Ok(mut pending) => pending.push(event.clone()),
Err(e) => log::warn!("notify: pending queue lock poisoned — event dropped: {e}"),
} }
match level { match level {
@ -48,3 +50,36 @@ pub fn take_pending() -> Vec<MatchedEvent> {
.map(|mut v| std::mem::take(&mut *v)) .map(|mut v| std::mem::take(&mut *v))
.unwrap_or_default() .unwrap_or_default()
} }
#[cfg(test)]
mod tests {
use super::*;
fn make_event(id: &str) -> MatchedEvent {
MatchedEvent {
pattern_id: id.into(),
title: "Title".into(),
body: "Body".into(),
severity: "warn".into(),
timestamp: 0,
}
}
#[test]
fn take_pending_drains_queue() {
// Ensure clean state — drain any events from other tests sharing the static
let _ = take_pending();
if let Ok(mut q) = PENDING.lock() {
q.push(make_event("a"));
q.push(make_event("b"));
}
let first = take_pending();
assert_eq!(first.len(), 2);
assert_eq!(first[0].pattern_id, "a");
let second = take_pending();
assert!(second.is_empty(), "queue must be empty after drain");
}
}