1use std::sync::atomic::{AtomicU64, Ordering};
2use std::sync::Arc;
3
4pub type EventCallbacks<E> = [Arc<dyn Fn(&E) + Send + Sync>];
24
25pub fn dispatch_event_callbacks<E>(owner: &str, callbacks: &EventCallbacks<E>, event: &E) -> usize {
26 let mut panics = 0;
27 for (index, callback) in callbacks.iter().enumerate() {
28 if let Err(payload) = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
29 callback(event);
30 })) {
31 panics += 1;
32 log::error!(
33 "{} callback #{} panicked: {}; continuing dispatch",
34 owner,
35 index,
36 panic_payload_message(&payload)
37 );
38 }
39 }
40 panics
41}
42
43#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
48pub struct DispatchOutcome {
49 pub delivered: usize,
50 pub skipped_by_filter: usize,
51 pub panics: usize,
52}
53
54#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
60pub struct TimedDispatchOutcome {
61 pub outcome: DispatchOutcome,
62 pub slow: usize,
63 pub slowest_ms: u64,
64}
65
66pub type SubscriptionId = u64;
72
73pub type EventFilter<E> = Arc<dyn Fn(&E) -> bool + Send + Sync>;
77
78struct SubscriptionEntry<E> {
79 id: SubscriptionId,
80 callback: Arc<dyn Fn(&E) + Send + Sync>,
81 filter: Option<EventFilter<E>>,
82}
83
84struct SnapshotEntry<E> {
85 callback: Arc<dyn Fn(&E) + Send + Sync>,
86 filter: Option<EventFilter<E>>,
87}
88
89enum FilterEval {
90 Matched,
91 Skip,
92 Panicked,
93}
94
95fn eval_filter<E>(
96 owner: &str,
97 index: usize,
98 filter: Option<&EventFilter<E>>,
99 event: &E,
100) -> FilterEval {
101 match filter {
102 None => FilterEval::Matched,
103 Some(filter) => {
104 match std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| filter(event))) {
105 Ok(true) => FilterEval::Matched,
106 Ok(false) => FilterEval::Skip,
107 Err(payload) => {
108 log::error!(
109 "{} filter #{} panicked: {}; skipping observer",
110 owner,
111 index,
112 panic_payload_message(&payload)
113 );
114 FilterEval::Panicked
115 }
116 }
117 }
118 }
119}
120
121fn invoke_callback<E>(
122 callback: &dyn Fn(&E),
123 event: &E,
124) -> Result<(), Box<dyn std::any::Any + Send>> {
125 std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| callback(event)))
126}
127
128pub struct EventSubscriptions<E> {
143 next_id: AtomicU64,
144 entries: parking_lot::RwLock<Vec<SubscriptionEntry<E>>>,
145}
146
147impl<E> Default for EventSubscriptions<E> {
148 fn default() -> Self {
149 Self::new()
150 }
151}
152
153impl<E> EventSubscriptions<E> {
154 pub fn new() -> Self {
155 Self {
156 next_id: AtomicU64::new(1),
157 entries: parking_lot::RwLock::new(Vec::new()),
158 }
159 }
160
161 pub fn add(&self, callback: Arc<dyn Fn(&E) + Send + Sync>) -> SubscriptionId {
163 self.add_filtered(callback, None)
164 }
165
166 pub fn add_filtered(
168 &self,
169 callback: Arc<dyn Fn(&E) + Send + Sync>,
170 filter: Option<EventFilter<E>>,
171 ) -> SubscriptionId {
172 let id = self.next_id.fetch_add(1, Ordering::SeqCst);
173 self.entries.write().push(SubscriptionEntry {
174 id,
175 callback,
176 filter,
177 });
178 id
179 }
180
181 pub fn remove(&self, id: SubscriptionId) -> bool {
183 let mut entries = self.entries.write();
184 let before = entries.len();
185 entries.retain(|entry| entry.id != id);
186 entries.len() != before
187 }
188
189 pub fn clear(&self) {
191 self.entries.write().clear();
192 }
193
194 pub fn subscribe(&self, callback: Arc<dyn Fn(&E) + Send + Sync>) -> SubscriptionGuard<'_, E> {
198 let id = self.add(callback);
199 SubscriptionGuard::new(self, id)
200 }
201
202 pub fn len(&self) -> usize {
204 self.entries.read().len()
205 }
206
207 pub fn is_empty(&self) -> bool {
209 self.entries.read().is_empty()
210 }
211
212 pub fn dispatch(&self, owner: &str, event: &E) -> usize {
216 self.dispatch_detailed(owner, event).panics
217 }
218
219 fn snapshot_locked(&self) -> Vec<SnapshotEntry<E>> {
220 let entries = self.entries.read();
221 if entries.is_empty() {
222 return Vec::new();
223 }
224 entries
225 .iter()
226 .map(|entry| SnapshotEntry {
227 callback: Arc::clone(&entry.callback),
228 filter: entry.filter.clone(),
229 })
230 .collect()
231 }
232
233 pub fn dispatch_detailed(&self, owner: &str, event: &E) -> DispatchOutcome {
235 let snapshot = self.snapshot_locked();
236 if snapshot.is_empty() {
237 return DispatchOutcome::default();
238 }
239 let mut outcome = DispatchOutcome::default();
240 for (index, item) in snapshot.iter().enumerate() {
241 match eval_filter(owner, index, item.filter.as_ref(), event) {
242 FilterEval::Skip => {
243 outcome.skipped_by_filter += 1;
244 continue;
245 }
246 FilterEval::Panicked => {
247 outcome.panics += 1;
248 outcome.skipped_by_filter += 1;
249 continue;
250 }
251 FilterEval::Matched => {}
252 }
253 match invoke_callback(item.callback.as_ref(), event) {
254 Ok(()) => {
255 outcome.delivered += 1;
256 }
257 Err(payload) => {
258 outcome.panics += 1;
259 log::error!(
260 "{} callback #{} panicked: {}; continuing dispatch",
261 owner,
262 index,
263 panic_payload_message(&payload)
264 );
265 continue;
266 }
267 }
268 }
269 outcome
270 }
271
272 pub fn dispatch_timed(
281 &self,
282 owner: &str,
283 event: &E,
284 slow_threshold: std::time::Duration,
285 ) -> TimedDispatchOutcome {
286 let snapshot = self.snapshot_locked();
287 if snapshot.is_empty() {
288 return TimedDispatchOutcome::default();
289 }
290 let mut result = TimedDispatchOutcome::default();
291 for (index, item) in snapshot.iter().enumerate() {
292 match eval_filter(owner, index, item.filter.as_ref(), event) {
293 FilterEval::Skip => {
294 result.outcome.skipped_by_filter += 1;
295 continue;
296 }
297 FilterEval::Panicked => {
298 result.outcome.panics += 1;
299 result.outcome.skipped_by_filter += 1;
300 continue;
301 }
302 FilterEval::Matched => {}
303 }
304 let start = std::time::Instant::now();
305 match invoke_callback(item.callback.as_ref(), event) {
306 Ok(()) => {
307 result.outcome.delivered += 1;
308 }
309 Err(payload) => {
310 result.outcome.panics += 1;
311 log::error!(
312 "{} callback #{} panicked: {}; continuing dispatch",
313 owner,
314 index,
315 panic_payload_message(&payload)
316 );
317 }
318 }
319 let elapsed = start.elapsed();
320 if elapsed >= slow_threshold {
321 result.slow += 1;
322 let elapsed_ms = elapsed.as_millis() as u64;
323 result.slowest_ms = result.slowest_ms.max(elapsed_ms);
324 log::warn!(
325 "{} callback #{} slow: {}ms >= {}ms threshold",
326 owner,
327 index,
328 elapsed_ms,
329 slow_threshold.as_millis() as u64,
330 );
331 }
332 }
333 result
334 }
335}
336
337pub struct SubscriptionGuard<'a, E> {
342 registry: Option<&'a EventSubscriptions<E>>,
343 id: SubscriptionId,
344}
345
346impl<'a, E> SubscriptionGuard<'a, E> {
347 fn new(registry: &'a EventSubscriptions<E>, id: SubscriptionId) -> Self {
348 Self {
349 registry: Some(registry),
350 id,
351 }
352 }
353
354 pub fn id(&self) -> SubscriptionId {
356 self.id
357 }
358
359 pub fn forget(mut self) -> SubscriptionId {
361 let id = self.id;
362 self.registry = None;
363 std::mem::forget(self);
364 id
365 }
366}
367
368impl<E> Drop for SubscriptionGuard<'_, E> {
369 fn drop(&mut self) {
370 if let Some(registry) = self.registry {
371 registry.remove(self.id);
372 }
373 }
374}
375
376pub fn panic_payload_message(payload: &Box<dyn std::any::Any + Send>) -> String {
377 if let Some(message) = payload.downcast_ref::<&str>() {
378 message.to_string()
379 } else if let Some(message) = payload.downcast_ref::<String>() {
380 message.clone()
381 } else {
382 "unknown panic payload".to_string()
383 }
384}
385
386pub struct AsyncForwarder<E> {
399 sender: std::sync::mpsc::SyncSender<E>,
400 dropped: std::sync::atomic::AtomicUsize,
401}
402
403impl<E> AsyncForwarder<E>
404where
405 E: Clone + Send + 'static,
406{
407 pub fn spawn(
414 capacity: usize,
415 callback: impl Fn(&E) + Send + 'static,
416 ) -> (Arc<Self>, std::thread::JoinHandle<()>) {
417 let (sender, receiver) = std::sync::mpsc::sync_channel(capacity);
418 let forwarder = Arc::new(Self {
419 sender,
420 dropped: std::sync::atomic::AtomicUsize::new(0),
421 });
422 let handle = std::thread::spawn(move || {
423 for event in receiver {
424 if invoke_callback(&callback, &event).is_err() {
425 log::error!("async forwarder callback panicked; continuing");
426 }
427 }
428 });
429 (forwarder, handle)
430 }
431
432 pub fn forward(&self, event: &E) {
435 if self.sender.try_send(event.clone()).is_err() {
436 self.dropped
437 .fetch_add(1, std::sync::atomic::Ordering::Relaxed);
438 }
439 }
440
441 pub fn dropped_count(&self) -> usize {
443 self.dropped.load(std::sync::atomic::Ordering::Relaxed)
444 }
445
446 pub fn adapter(self: &Arc<Self>) -> Arc<dyn Fn(&E) + Send + Sync> {
448 let forwarder = Arc::clone(self);
449 Arc::new(move |event: &E| forwarder.forward(event))
450 }
451}
452
453#[cfg(test)]
454mod tests {
455 use super::*;
456 use std::sync::atomic::{AtomicUsize, Ordering};
457
458 #[test]
459 fn dispatch_reaches_all_and_isolates_panic() {
460 let registry: EventSubscriptions<u32> = EventSubscriptions::new();
461 let hits = Arc::new(AtomicUsize::new(0));
462 let first = Arc::clone(&hits);
463 registry.add(Arc::new(move |_| {
464 first.fetch_add(1, Ordering::SeqCst);
465 }));
466 registry.add(Arc::new(|_: &u32| panic!("boom")));
467 let last = Arc::clone(&hits);
468 registry.add(Arc::new(move |_| {
469 last.fetch_add(1, Ordering::SeqCst);
470 }));
471 let panics = registry.dispatch("test", &1);
472 assert_eq!(panics, 1);
473 assert_eq!(hits.load(Ordering::SeqCst), 2);
474 }
475
476 #[test]
477 fn remove_and_filter_are_honored() {
478 let registry: EventSubscriptions<u32> = EventSubscriptions::new();
479 let hits = Arc::new(AtomicUsize::new(0));
480 let probe = Arc::clone(&hits);
481 let id = registry.add(Arc::new(move |_| {
482 probe.fetch_add(1, Ordering::SeqCst);
483 }));
484 assert_eq!(registry.len(), 1);
485 assert!(registry.remove(id));
486 assert!(!registry.remove(id));
487 assert_eq!(registry.dispatch("test", &1), 0);
488 assert_eq!(hits.load(Ordering::SeqCst), 0);
489
490 let even_probe = Arc::clone(&hits);
491 registry.add_filtered(
492 Arc::new(move |_| {
493 even_probe.fetch_add(1, Ordering::SeqCst);
494 }),
495 Some(Arc::new(|event: &u32| event.is_multiple_of(2))),
496 );
497 assert_eq!(registry.dispatch("test", &1), 0);
498 assert_eq!(registry.dispatch("test", &2), 0);
499 assert_eq!(hits.load(Ordering::SeqCst), 1);
500 }
501
502 #[test]
503 fn panicking_filter_skips_observer_without_aborting_dispatch() {
504 let registry: EventSubscriptions<u32> = EventSubscriptions::new();
505 let hits = Arc::new(AtomicUsize::new(0));
506 let bad_probe = Arc::clone(&hits);
507 registry.add_filtered(
508 Arc::new(move |_| {
509 bad_probe.fetch_add(1, Ordering::SeqCst);
510 }),
511 Some(Arc::new(|_: &u32| panic!("filter boom"))),
512 );
513 let good_probe = Arc::clone(&hits);
514 registry.add(Arc::new(move |_| {
515 good_probe.fetch_add(1, Ordering::SeqCst);
516 }));
517 let outcome = registry.dispatch_detailed("test", &1);
518 assert_eq!(outcome.panics, 1);
519 assert_eq!(outcome.delivered, 1);
520 assert_eq!(outcome.skipped_by_filter, 1);
521 assert_eq!(hits.load(Ordering::SeqCst), 1);
522 }
523
524 #[test]
525 fn dispatch_does_not_hold_lock_during_callbacks() {
526 let registry: Arc<EventSubscriptions<u32>> = Arc::new(EventSubscriptions::new());
527 let nested: Arc<EventSubscriptions<u32>> = Arc::clone(®istry);
528 let hits = Arc::new(AtomicUsize::new(0));
529 let probe = Arc::clone(&hits);
530 registry.add(Arc::new(move |_| {
531 probe.fetch_add(1, Ordering::SeqCst);
532 nested.add(Arc::new(|_| {}));
533 }));
534 let nested_filter: Arc<EventSubscriptions<u32>> = Arc::clone(®istry);
535 registry.add_filtered(
536 Arc::new(|_| {}),
537 Some(Arc::new(move |_| {
538 nested_filter.len();
539 true
540 })),
541 );
542 let outcome = registry.dispatch_detailed("test", &1);
543 assert_eq!(outcome.delivered, 2);
544 assert_eq!(outcome.panics, 0);
545 assert_eq!(hits.load(Ordering::SeqCst), 1);
546 assert_eq!(registry.len(), 3);
547 }
548
549 #[test]
550 fn subscription_guard_removes_on_drop_and_clear_empties() {
551 let registry: EventSubscriptions<u32> = EventSubscriptions::new();
552 {
553 let _guard = registry.subscribe(Arc::new(|_: &u32| {}));
554 assert_eq!(registry.len(), 1);
555 }
556 assert_eq!(registry.len(), 0);
557 registry.add(Arc::new(|_: &u32| {}));
558 registry.add(Arc::new(|_: &u32| {}));
559 assert_eq!(registry.len(), 2);
560 registry.clear();
561 assert!(registry.is_empty());
562 }
563
564 #[test]
565 fn timed_dispatch_flags_slow_observer() {
566 use std::time::Duration;
567 let registry: EventSubscriptions<u32> = EventSubscriptions::new();
568 registry.add(Arc::new(|_| {}));
569 registry.add(Arc::new(|_: &u32| {
570 std::thread::sleep(Duration::from_millis(15));
571 }));
572 let outcome = registry.dispatch_timed("test", &1, Duration::from_millis(5));
573 assert_eq!(outcome.outcome.delivered, 2);
574 assert_eq!(outcome.outcome.panics, 0);
575 assert_eq!(outcome.slow, 1);
576 assert!(outcome.slowest_ms >= 5);
577
578 let empty: EventSubscriptions<u32> = EventSubscriptions::new();
580 assert_eq!(
581 empty.dispatch_timed("test", &1, Duration::from_millis(1)),
582 TimedDispatchOutcome::default()
583 );
584 }
585
586 #[test]
587 fn async_forwarder_delivers_without_blocking_emitter() {
588 let registry: EventSubscriptions<u32> = EventSubscriptions::new();
589 let received = Arc::new(AtomicUsize::new(0));
590 let probe = Arc::clone(&received);
591 let (forwarder, handle) = AsyncForwarder::spawn(16, move |_: &u32| {
592 probe.fetch_add(1, Ordering::SeqCst);
593 });
594 registry.add(forwarder.adapter());
595 for event in 0..5u32 {
596 registry.dispatch("test", &event);
597 }
598 assert_eq!(forwarder.dropped_count(), 0);
599 drop(registry);
600 drop(forwarder);
601 handle.join().expect("worker joins");
602 assert_eq!(received.load(Ordering::SeqCst), 5);
603 }
604
605 #[test]
606 fn async_forwarder_counts_drops_on_full_queue() {
607 let received = Arc::new(AtomicUsize::new(0));
611 let probe = Arc::clone(&received);
612 let (forwarder, handle) = AsyncForwarder::spawn(0, move |_: &u32| {
613 probe.fetch_add(1, Ordering::SeqCst);
614 std::thread::sleep(std::time::Duration::from_millis(5));
615 });
616 for event in 0..10u32 {
617 forwarder.forward(&event);
618 }
619 let dropped = forwarder.dropped_count();
620 drop(forwarder);
621 handle.join().expect("worker joins");
622 assert_eq!(received.load(Ordering::SeqCst) + dropped, 10);
623 }
624}