1use crate::application::{
7 ports::Storage,
8 registry::{EventState, SuppressionRegistry},
9};
10use crate::domain::{
11 signature::EventSignature,
12 summary::{ClaimedSuppressions, SuppressionSummary},
13};
14use std::time::Duration;
15
16#[cfg(feature = "async")]
17use tokio::{sync::watch, time::interval};
18
19#[derive(Debug, Clone, PartialEq, Eq)]
21pub enum EmitterConfigError {
22 ZeroSummaryInterval,
24}
25
26impl std::fmt::Display for EmitterConfigError {
27 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
28 match self {
29 EmitterConfigError::ZeroSummaryInterval => {
30 write!(f, "summary interval must be greater than 0")
31 }
32 }
33 }
34}
35
36impl std::error::Error for EmitterConfigError {}
37
38#[cfg(feature = "async")]
40#[derive(Debug, Clone, PartialEq, Eq)]
41pub enum ShutdownError {
42 TaskPanicked,
44 TaskCancelled,
46 Timeout,
48 SignalFailed,
50}
51
52#[cfg(feature = "async")]
53impl std::fmt::Display for ShutdownError {
54 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
55 match self {
56 ShutdownError::TaskPanicked => write!(f, "emitter task panicked during shutdown"),
57 ShutdownError::TaskCancelled => write!(f, "emitter task was cancelled"),
58 ShutdownError::Timeout => write!(f, "shutdown exceeded timeout"),
59 ShutdownError::SignalFailed => write!(f, "failed to send shutdown signal"),
60 }
61 }
62}
63
64#[cfg(feature = "async")]
65impl std::error::Error for ShutdownError {}
66
67#[derive(Debug, Clone)]
69pub struct EmitterConfig {
70 pub interval: Duration,
72 pub min_count: usize,
74}
75
76impl Default for EmitterConfig {
77 fn default() -> Self {
78 Self {
79 interval: Duration::from_secs(30),
80 min_count: 1,
81 }
82 }
83}
84
85impl EmitterConfig {
86 pub fn new(interval: Duration) -> Result<Self, EmitterConfigError> {
91 if interval.is_zero() {
92 return Err(EmitterConfigError::ZeroSummaryInterval);
93 }
94 Ok(Self {
95 interval,
96 min_count: 1,
97 })
98 }
99
100 pub fn with_min_count(mut self, min_count: usize) -> Self {
102 self.min_count = min_count;
103 self
104 }
105}
106
107#[cfg(feature = "async")]
143pub struct EmitterHandle {
144 shutdown_tx: watch::Sender<bool>,
145 join_handle: Option<tokio::task::JoinHandle<()>>,
146}
147
148#[cfg(feature = "async")]
149impl EmitterHandle {
150 pub async fn shutdown(self) -> Result<(), ShutdownError> {
189 self.shutdown_with_timeout(Duration::from_secs(10)).await
190 }
191
192 pub async fn shutdown_with_timeout(
234 mut self,
235 timeout_duration: Duration,
236 ) -> Result<(), ShutdownError> {
237 use tokio::time::timeout;
238
239 if self.shutdown_tx.send(true).is_err() {
241 return Err(ShutdownError::SignalFailed);
242 }
243
244 if let Some(handle) = self.join_handle.take() {
246 match timeout(timeout_duration, handle).await {
247 Ok(Ok(())) => Ok(()),
248 Ok(Err(e)) if e.is_panic() => Err(ShutdownError::TaskPanicked),
249 Ok(Err(e)) if e.is_cancelled() => Err(ShutdownError::TaskCancelled),
250 Ok(Err(_)) => Err(ShutdownError::TaskPanicked), Err(_) => Err(ShutdownError::Timeout),
252 }
253 } else {
254 Ok(())
255 }
256 }
257
258 pub fn is_running(&self) -> bool {
260 self.join_handle.as_ref().is_some_and(|h| !h.is_finished())
261 }
262}
263
264pub struct SummaryEmitter<S>
266where
267 S: Storage<EventSignature, EventState> + Clone,
268{
269 registry: SuppressionRegistry<S>,
270 config: EmitterConfig,
271}
272
273struct ClaimedSummary {
274 signature: EventSignature,
275 claim: ClaimedSuppressions,
276}
277
278struct ClaimedSummaryBatch {
279 summaries: Vec<SuppressionSummary>,
280 claims: Vec<ClaimedSummary>,
281}
282
283impl<S> SummaryEmitter<S>
284where
285 S: Storage<EventSignature, EventState> + Clone,
286{
287 pub fn new(registry: SuppressionRegistry<S>, config: EmitterConfig) -> Self {
289 Self { registry, config }
290 }
291
292 pub fn collect_summaries(&self) -> Vec<SuppressionSummary> {
297 self.collect_claimed_summaries().summaries
298 }
299
300 fn collect_claimed_summaries(&self) -> ClaimedSummaryBatch {
301 let mut summaries = Vec::new();
302 let mut claims = Vec::new();
303 let min_count = self.config.min_count;
304
305 self.registry.cleanup(|signature, state| {
306 if let Some(claim) = state.counter.claim_unreported(min_count) {
307 #[cfg(feature = "human-readable")]
308 let summary = SuppressionSummary::from_claim_with_metadata(
309 *signature,
310 claim.clone(),
311 state.metadata.clone(),
312 );
313
314 #[cfg(not(feature = "human-readable"))]
315 let summary = SuppressionSummary::from_claim(*signature, claim.clone());
316
317 claims.push(ClaimedSummary {
318 signature: *signature,
319 claim,
320 });
321 summaries.push(summary);
322 }
323
324 true
325 });
326
327 ClaimedSummaryBatch { summaries, claims }
328 }
329
330 fn rollback_claimed_summaries(&self, claims: &[ClaimedSummary]) {
331 if claims.is_empty() {
332 return;
333 }
334
335 self.registry.cleanup(|signature, state| {
336 for claim in claims {
337 if claim.signature == *signature {
338 state.counter.rollback_claim(&claim.claim);
339 }
340 }
341
342 true
343 });
344 }
345
346 fn emit_claimed_summaries<F>(&self, batch: ClaimedSummaryBatch, emit_fn: &mut F) -> bool
347 where
348 F: FnMut(Vec<SuppressionSummary>),
349 {
350 if batch.summaries.is_empty() {
351 return true;
352 }
353
354 let claims = batch.claims;
355 let summaries = batch.summaries;
356
357 let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
358 emit_fn(summaries);
359 }));
360
361 if result.is_err() {
362 self.rollback_claimed_summaries(&claims);
363 false
364 } else {
365 true
366 }
367 }
368
369 #[cfg(feature = "async")]
424 pub fn start<F>(self, mut emit_fn: F, emit_final: bool) -> EmitterHandle
425 where
426 F: FnMut(Vec<SuppressionSummary>) + Send + 'static,
427 S: Send + 'static,
428 {
429 let (shutdown_tx, mut shutdown_rx) = tokio::sync::watch::channel(false);
430
431 let handle = tokio::spawn(async move {
432 let mut ticker = interval(self.config.interval);
433 ticker.set_missed_tick_behavior(tokio::time::MissedTickBehavior::Skip);
435
436 loop {
437 tokio::select! {
438 biased;
440
441 _ = shutdown_rx.changed() => {
442 if *shutdown_rx.borrow_and_update() {
443 if emit_final {
445 let batch = self.collect_claimed_summaries();
446 if !self.emit_claimed_summaries(batch, &mut emit_fn) {
447 #[cfg(debug_assertions)]
448 eprintln!("Warning: emit_fn panicked during final emission");
449 }
450 }
451 break;
452 }
453 }
454 _ = ticker.tick() => {
455 let batch = self.collect_claimed_summaries();
456 if !self.emit_claimed_summaries(batch, &mut emit_fn) {
457 #[cfg(debug_assertions)]
459 eprintln!("Warning: emit_fn panicked during emission");
460 }
461 }
462 }
463 }
464 });
465
466 EmitterHandle {
467 shutdown_tx,
468 join_handle: Some(handle),
469 }
470 }
471
472 pub fn config(&self) -> &EmitterConfig {
474 &self.config
475 }
476
477 pub fn registry(&self) -> &SuppressionRegistry<S> {
479 &self.registry
480 }
481}
482
483#[cfg(test)]
484mod tests {
485 use super::*;
486 use crate::domain::{policy::Policy, signature::EventSignature};
487 use crate::infrastructure::clock::SystemClock;
488 use crate::infrastructure::storage::ShardedStorage;
489 use std::sync::Arc;
490
491 #[test]
492 fn test_collect_summaries_empty() {
493 let storage = Arc::new(ShardedStorage::new());
494 let clock = Arc::new(SystemClock::new());
495 let policy = Policy::count_based(100).unwrap();
496 let registry = SuppressionRegistry::new(storage, clock, policy);
497 let config = EmitterConfig::default();
498 let emitter = SummaryEmitter::new(registry.clone(), config);
499
500 let summaries = emitter.collect_summaries();
501 assert!(summaries.is_empty());
502 }
503
504 #[test]
505 fn test_collect_summaries_with_suppressions() {
506 let storage = Arc::new(ShardedStorage::new());
507 let clock = Arc::new(SystemClock::new());
508 let policy = Policy::count_based(100).unwrap();
509 let registry = SuppressionRegistry::new(storage, clock, policy);
510 let config = EmitterConfig::default();
511
512 for i in 0..3 {
514 let sig = EventSignature::simple("INFO", &format!("Message {}", i));
515 registry.with_event_state(sig, |state, now| {
516 for _ in 0..(i + 1) * 5 {
518 state.counter.record_suppression(now);
519 }
520 });
521 }
522
523 let emitter = SummaryEmitter::new(registry.clone(), config);
524 let summaries = emitter.collect_summaries();
525
526 assert_eq!(summaries.len(), 3);
527
528 let counts: Vec<usize> = summaries.iter().map(|s| s.count).collect();
530 assert!(counts.contains(&5));
531 assert!(counts.contains(&10));
532 assert!(counts.contains(&15));
533
534 let summaries = emitter.collect_summaries();
535 assert!(
536 summaries.is_empty(),
537 "already emitted summaries should not be emitted again"
538 );
539 }
540
541 #[test]
542 fn test_min_count_filtering() {
543 let storage = Arc::new(ShardedStorage::new());
544 let clock = Arc::new(SystemClock::new());
545 let policy = Policy::count_based(100).unwrap();
546 let registry = SuppressionRegistry::new(storage, clock, policy);
547 let config = EmitterConfig::default().with_min_count(10);
548
549 let sig1 = EventSignature::simple("INFO", "Low count");
551 registry.with_event_state(sig1, |state, now| {
552 for _ in 0..4 {
553 state.counter.record_suppression(now);
554 }
555 });
556
557 let sig2 = EventSignature::simple("INFO", "High count");
559 registry.with_event_state(sig2, |state, now| {
560 for _ in 0..14 {
561 state.counter.record_suppression(now);
562 }
563 });
564
565 let emitter = SummaryEmitter::new(registry.clone(), config);
566 let summaries = emitter.collect_summaries();
567
568 assert_eq!(summaries.len(), 1);
570 assert_eq!(summaries[0].count, 14);
571
572 let summaries = emitter.collect_summaries();
573 assert!(
574 summaries.is_empty(),
575 "claimed summaries should not be emitted again"
576 );
577 }
578
579 #[test]
580 fn test_min_count_accumulates_unreported_suppressions() {
581 let storage = Arc::new(ShardedStorage::new());
582 let clock = Arc::new(SystemClock::new());
583 let policy = Policy::count_based(100).unwrap();
584 let registry = SuppressionRegistry::new(storage, clock, policy);
585 let config = EmitterConfig::default().with_min_count(10);
586
587 let sig = EventSignature::simple("INFO", "Accumulated");
588 registry.with_event_state(sig, |state, now| {
589 for _ in 0..4 {
590 state.counter.record_suppression(now);
591 }
592 });
593
594 let emitter = SummaryEmitter::new(registry.clone(), config);
595 assert!(emitter.collect_summaries().is_empty());
596
597 registry.with_event_state(sig, |state, now| {
598 for _ in 0..6 {
599 state.counter.record_suppression(now);
600 }
601 });
602
603 let summaries = emitter.collect_summaries();
604 assert_eq!(summaries.len(), 1);
605 assert_eq!(summaries[0].count, 10);
606 }
607
608 #[test]
609 fn test_collect_summaries_reports_only_new_suppressions() {
610 let storage = Arc::new(ShardedStorage::new());
611 let clock = Arc::new(SystemClock::new());
612 let policy = Policy::count_based(100).unwrap();
613 let registry = SuppressionRegistry::new(storage, clock, policy);
614 let config = EmitterConfig::default();
615
616 let sig = EventSignature::simple("INFO", "Delta");
617 registry.with_event_state(sig, |state, now| {
618 for _ in 0..5 {
619 state.counter.record_suppression(now);
620 }
621 });
622
623 let emitter = SummaryEmitter::new(registry.clone(), config);
624 let summaries = emitter.collect_summaries();
625 assert_eq!(summaries.len(), 1);
626 assert_eq!(summaries[0].count, 5);
627
628 assert!(emitter.collect_summaries().is_empty());
629
630 registry.with_event_state(sig, |state, now| {
631 for _ in 0..3 {
632 state.counter.record_suppression(now);
633 }
634 });
635
636 let summaries = emitter.collect_summaries();
637 assert_eq!(summaries.len(), 1);
638 assert_eq!(summaries[0].count, 3);
639 }
640
641 #[cfg(feature = "async")]
642 #[tokio::test]
643 async fn test_async_emission() {
644 use std::sync::Mutex;
645
646 let storage = Arc::new(ShardedStorage::new());
647 let clock = Arc::new(SystemClock::new());
648 let policy = Policy::count_based(100).unwrap();
649 let registry = SuppressionRegistry::new(storage, clock, policy);
650 let config = EmitterConfig::new(Duration::from_millis(100)).unwrap();
651
652 let sig = EventSignature::simple("INFO", "Test");
654 registry.with_event_state(sig, |state, now| {
655 state.counter.record_suppression(now);
656 });
657
658 let emitter = SummaryEmitter::new(registry.clone(), config);
659
660 let emissions = Arc::new(Mutex::new(Vec::new()));
662 let emissions_clone = Arc::clone(&emissions);
663
664 let handle = emitter.start(
665 move |summaries| {
666 emissions_clone.lock().unwrap().push(summaries.len());
667 },
668 false,
669 );
670
671 tokio::time::sleep(Duration::from_millis(250)).await;
673
674 handle.shutdown().await.expect("shutdown failed");
675
676 let emission_count = emissions.lock().unwrap().len();
678 assert_eq!(emission_count, 1);
679 }
680
681 #[test]
682 fn test_emitter_config_zero_interval() {
683 let result = EmitterConfig::new(Duration::from_secs(0));
684 assert!(matches!(
685 result,
686 Err(EmitterConfigError::ZeroSummaryInterval)
687 ));
688 }
689
690 #[test]
691 fn test_emitter_config_valid_interval() {
692 let config = EmitterConfig::new(Duration::from_secs(30)).unwrap();
693 assert_eq!(config.interval, Duration::from_secs(30));
694 assert_eq!(config.min_count, 1);
695 }
696
697 #[cfg(feature = "async")]
698 #[tokio::test]
699 async fn test_graceful_shutdown() {
700 use std::sync::Mutex;
701
702 let storage = Arc::new(ShardedStorage::new());
703 let clock = Arc::new(SystemClock::new());
704 let policy = Policy::count_based(100).unwrap();
705 let registry = SuppressionRegistry::new(storage, clock, policy);
706 let config = EmitterConfig::new(Duration::from_millis(100)).unwrap();
707
708 let sig = EventSignature::simple("INFO", "Test");
710 registry.with_event_state(sig, |state, now| {
711 state.counter.record_suppression(now);
712 });
713
714 let emitter = SummaryEmitter::new(registry.clone(), config);
715
716 let emissions = Arc::new(Mutex::new(0));
717 let emissions_clone = Arc::clone(&emissions);
718
719 let handle = emitter.start(
720 move |_| {
721 *emissions_clone.lock().unwrap() += 1;
722 },
723 false,
724 );
725
726 tokio::time::sleep(Duration::from_millis(250)).await;
728
729 handle.shutdown().await.expect("shutdown failed");
731
732 let final_count = *emissions.lock().unwrap();
734 assert!(final_count >= 1);
735
736 tokio::time::sleep(Duration::from_millis(150)).await;
738 let count_after_shutdown = *emissions.lock().unwrap();
739 assert_eq!(count_after_shutdown, final_count);
740 }
741
742 #[cfg(feature = "async")]
743 #[tokio::test]
744 async fn test_shutdown_with_final_emission() {
745 use std::sync::Mutex;
746
747 let storage = Arc::new(ShardedStorage::new());
748 let clock = Arc::new(SystemClock::new());
749 let policy = Policy::count_based(100).unwrap();
750 let registry = SuppressionRegistry::new(storage, clock, policy);
751 let config = EmitterConfig::new(Duration::from_secs(60)).unwrap(); let emitter = SummaryEmitter::new(registry.clone(), config);
754
755 let emissions = Arc::new(Mutex::new(Vec::new()));
756 let emissions_clone = Arc::clone(&emissions);
757
758 let handle = emitter.start(
759 move |summaries| {
760 emissions_clone.lock().unwrap().push(summaries.len());
761 },
762 true, );
764
765 tokio::time::sleep(Duration::from_millis(50)).await;
767
768 let sig = EventSignature::simple("INFO", "Test event");
770 registry.with_event_state(sig, |state, now| {
771 for _ in 0..10 {
772 state.counter.record_suppression(now);
773 }
774 });
775
776 tokio::time::sleep(Duration::from_millis(50)).await;
778 handle.shutdown().await.expect("shutdown failed");
779
780 let emission_list = emissions.lock().unwrap();
782 assert_eq!(emission_list.len(), 1);
783 assert_eq!(emission_list[0], 1); }
785
786 #[cfg(feature = "async")]
787 #[tokio::test]
788 async fn test_shutdown_without_final_emission() {
789 use std::sync::Mutex;
790
791 let storage = Arc::new(ShardedStorage::new());
792 let clock = Arc::new(SystemClock::new());
793 let policy = Policy::count_based(100).unwrap();
794 let registry = SuppressionRegistry::new(storage, clock, policy);
795 let config = EmitterConfig::new(Duration::from_secs(60)).unwrap();
796
797 let emitter = SummaryEmitter::new(registry.clone(), config);
798
799 let emissions = Arc::new(Mutex::new(0));
800 let emissions_clone = Arc::clone(&emissions);
801
802 let handle = emitter.start(
803 move |_| {
804 *emissions_clone.lock().unwrap() += 1;
805 },
806 false, );
808
809 tokio::time::sleep(Duration::from_millis(50)).await;
811
812 let sig = EventSignature::simple("INFO", "Test event");
814 registry.with_event_state(sig, |state, now| {
815 state.counter.record_suppression(now);
816 });
817
818 tokio::time::sleep(Duration::from_millis(50)).await;
820 handle.shutdown().await.expect("shutdown failed");
821
822 assert_eq!(*emissions.lock().unwrap(), 0);
824 }
825
826 #[cfg(feature = "async")]
827 #[tokio::test]
828 async fn test_is_running() {
829 let storage = Arc::new(ShardedStorage::new());
830 let clock = Arc::new(SystemClock::new());
831 let policy = Policy::count_based(100).unwrap();
832 let registry = SuppressionRegistry::new(storage, clock, policy);
833 let config = EmitterConfig::new(Duration::from_millis(100)).unwrap();
834
835 let emitter = SummaryEmitter::new(registry.clone(), config);
836 let handle = emitter.start(|_| {}, false);
837
838 assert!(handle.is_running());
840
841 handle.shutdown().await.expect("shutdown failed");
843
844 tokio::time::sleep(Duration::from_millis(50)).await;
846 }
848
849 #[cfg(feature = "async")]
850 #[tokio::test]
851 async fn test_shutdown_during_emission() {
852 use std::sync::{Arc, Mutex};
853
854 let storage = Arc::new(ShardedStorage::new());
855 let clock = Arc::new(SystemClock::new());
856 let policy = Policy::count_based(100).unwrap();
857 let registry = SuppressionRegistry::new(storage, clock, policy);
858 let config = EmitterConfig::new(Duration::from_millis(50)).unwrap();
859
860 let sig = EventSignature::simple("INFO", "Test");
862 registry.with_event_state(sig, |state, now| {
863 state.counter.record_suppression(now);
864 });
865
866 let emitter = SummaryEmitter::new(registry, config);
867
868 let emissions = Arc::new(Mutex::new(0));
869 let emissions_clone = Arc::clone(&emissions);
870
871 let handle = emitter.start(
872 move |_| {
873 std::thread::sleep(Duration::from_millis(30));
875 *emissions_clone.lock().unwrap() += 1;
876 },
877 false,
878 );
879
880 tokio::time::sleep(Duration::from_millis(60)).await;
882
883 handle.shutdown().await.expect("shutdown failed");
885
886 assert!(*emissions.lock().unwrap() >= 1);
888 }
889
890 #[cfg(feature = "async")]
891 #[tokio::test]
892 async fn test_shutdown_with_custom_timeout() {
893 let storage = Arc::new(ShardedStorage::new());
894 let clock = Arc::new(SystemClock::new());
895 let policy = Policy::count_based(100).unwrap();
896 let registry = SuppressionRegistry::new(storage, clock, policy);
897 let config = EmitterConfig::new(Duration::from_millis(100)).unwrap();
898
899 let emitter = SummaryEmitter::new(registry, config);
900 let handle = emitter.start(|_| {}, false);
901
902 tokio::time::sleep(Duration::from_millis(150)).await;
904
905 let result = handle.shutdown_with_timeout(Duration::from_secs(5)).await;
907 assert!(result.is_ok());
908 }
909
910 #[cfg(feature = "async")]
911 #[tokio::test]
912 async fn test_panic_in_emit_fn() {
913 use std::sync::atomic::{AtomicUsize, Ordering};
914 use std::sync::Mutex;
915
916 let storage = Arc::new(ShardedStorage::new());
917 let clock = Arc::new(SystemClock::new());
918 let policy = Policy::count_based(100).unwrap();
919 let registry = SuppressionRegistry::new(storage, clock, policy);
920 let config = EmitterConfig::new(Duration::from_millis(50)).unwrap();
921
922 let sig = EventSignature::simple("INFO", "Test");
924 registry.with_event_state(sig, |state, now| {
925 for _ in 0..5 {
926 state.counter.record_suppression(now);
927 }
928 });
929
930 let emitter = SummaryEmitter::new(registry.clone(), config);
931
932 let call_count = Arc::new(AtomicUsize::new(0));
933 let call_count_clone = Arc::clone(&call_count);
934 let emitted_counts = Arc::new(Mutex::new(Vec::new()));
935 let emitted_counts_clone = Arc::clone(&emitted_counts);
936
937 let handle = emitter.start(
938 move |summaries| {
939 let total: usize = summaries.iter().map(|summary| summary.count).sum();
940 emitted_counts_clone.lock().unwrap().push(total);
941 let count = call_count_clone.fetch_add(1, Ordering::SeqCst);
942
943 if count == 0 {
945 panic!("intentional panic for testing");
946 }
947 },
949 false,
950 );
951
952 tokio::time::sleep(Duration::from_millis(150)).await;
954
955 handle.shutdown().await.expect("shutdown failed");
956
957 let final_count = call_count.load(Ordering::SeqCst);
959 assert!(
960 final_count > 1,
961 "Task should continue after panic in emit_fn"
962 );
963
964 let emitted_counts = emitted_counts.lock().unwrap();
965 assert!(
966 emitted_counts.len() > 1,
967 "Panicked emission should be retried"
968 );
969 assert_eq!(emitted_counts[0], 5);
970 assert_eq!(emitted_counts[1], 5);
971 }
972
973 #[cfg(feature = "async")]
974 #[tokio::test]
975 async fn test_repeated_panic_in_emit_fn() {
976 use std::sync::atomic::{AtomicUsize, Ordering};
977
978 let storage = Arc::new(ShardedStorage::new());
979 let clock = Arc::new(SystemClock::new());
980 let policy = Policy::count_based(100).unwrap();
981 let registry = SuppressionRegistry::new(storage, clock, policy);
982 let config = EmitterConfig::new(Duration::from_millis(30)).unwrap();
983
984 let sig = EventSignature::simple("INFO", "Test");
985 registry.with_event_state(sig, |state, now| {
986 state.counter.record_suppression(now);
987 });
988
989 let emitter = SummaryEmitter::new(registry.clone(), config);
990
991 let call_count = Arc::new(AtomicUsize::new(0));
992 let call_count_clone = Arc::clone(&call_count);
993
994 let handle = emitter.start(
995 move |_summaries| {
996 call_count_clone.fetch_add(1, Ordering::SeqCst);
997 panic!("always panic");
998 },
999 false,
1000 );
1001
1002 tokio::time::sleep(Duration::from_millis(70)).await;
1003
1004 for _ in 0..3 {
1005 let sig = EventSignature::simple("INFO", "Test");
1006 registry.with_event_state(sig, |state, now| {
1007 state.counter.record_suppression(now);
1008 });
1009 tokio::time::sleep(Duration::from_millis(40)).await;
1010 }
1011
1012 handle.shutdown().await.expect("shutdown failed");
1013
1014 let final_count = call_count.load(Ordering::SeqCst);
1016 assert!(
1017 final_count >= 3,
1018 "Task should continue despite repeated panics, got {} calls",
1019 final_count
1020 );
1021 }
1022
1023 #[cfg(feature = "async")]
1024 #[tokio::test]
1025 async fn test_panic_during_final_emission() {
1026 use std::sync::atomic::{AtomicBool, Ordering};
1027
1028 let storage = Arc::new(ShardedStorage::new());
1029 let clock = Arc::new(SystemClock::new());
1030 let policy = Policy::count_based(100).unwrap();
1031 let registry = SuppressionRegistry::new(storage, clock, policy);
1032 let config = EmitterConfig::new(Duration::from_secs(3600)).unwrap(); let sig = EventSignature::simple("INFO", "Test");
1035 registry.with_event_state(sig, |state, now| {
1036 state.counter.record_suppression(now);
1037 });
1038
1039 let emitter = SummaryEmitter::new(registry, config);
1040
1041 let panicked = Arc::new(AtomicBool::new(false));
1042 let panicked_clone = Arc::clone(&panicked);
1043
1044 let handle = emitter.start(
1045 move |_summaries| {
1046 panicked_clone.store(true, Ordering::SeqCst);
1047 panic!("panic during final emission");
1048 },
1049 true, );
1051
1052 handle
1054 .shutdown()
1055 .await
1056 .expect("shutdown should succeed even if final emission panics");
1057
1058 assert!(
1060 panicked.load(Ordering::SeqCst),
1061 "Final emission should have been attempted"
1062 );
1063 }
1064
1065 #[cfg(feature = "async")]
1066 #[tokio::test]
1067 async fn test_shutdown_timeout_with_slow_emit_fn() {
1068 let storage = Arc::new(ShardedStorage::new());
1069 let clock = Arc::new(SystemClock::new());
1070 let policy = Policy::count_based(100).unwrap();
1071 let registry = SuppressionRegistry::new(storage, clock, policy);
1072 let config = EmitterConfig::new(Duration::from_secs(3600)).unwrap();
1073
1074 let emitter = SummaryEmitter::new(registry, config);
1075
1076 let handle = emitter.start(
1077 move |_summaries| {
1078 std::thread::sleep(Duration::from_millis(500));
1080 },
1081 true,
1082 );
1083
1084 let result = handle
1086 .shutdown_with_timeout(Duration::from_millis(10))
1087 .await;
1088
1089 let _ = result; }
1093
1094 #[cfg(feature = "async")]
1095 #[tokio::test]
1096 async fn test_handle_dropped_without_shutdown() {
1097 let storage = Arc::new(ShardedStorage::new());
1098 let clock = Arc::new(SystemClock::new());
1099 let policy = Policy::count_based(100).unwrap();
1100 let registry = SuppressionRegistry::new(storage, clock, policy);
1101 let config = EmitterConfig::new(Duration::from_millis(50)).unwrap();
1102
1103 let emitter = SummaryEmitter::new(registry, config);
1104
1105 let handle = emitter.start(|_summaries| {}, false);
1106
1107 drop(handle);
1109
1110 tokio::time::sleep(Duration::from_millis(100)).await;
1113
1114 }
1116
1117 #[cfg(feature = "async")]
1118 #[tokio::test]
1119 async fn test_concurrent_shutdown_calls() {
1120 let storage = Arc::new(ShardedStorage::new());
1121 let clock = Arc::new(SystemClock::new());
1122 let policy = Policy::count_based(100).unwrap();
1123 let registry = SuppressionRegistry::new(storage, clock, policy);
1124 let config = EmitterConfig::new(Duration::from_secs(3600)).unwrap();
1125
1126 let emitter = SummaryEmitter::new(registry, config);
1127 let handle = emitter.start(|_summaries| {}, false);
1128
1129 let sender = handle.shutdown_tx.clone();
1131 let mut handles_vec = vec![];
1132
1133 for _ in 0..5 {
1135 let sender_clone = sender.clone();
1136 handles_vec.push(tokio::spawn(async move {
1137 let _ = sender_clone.send(true);
1138 }));
1139 }
1140
1141 for h in handles_vec {
1143 let _ = h.await;
1144 }
1145
1146 let _ = handle.shutdown().await;
1148 }
1149
1150 #[cfg(feature = "async")]
1151 #[tokio::test]
1152 async fn test_shutdown_signal_failure() {
1153 let storage = Arc::new(ShardedStorage::new());
1155 let clock = Arc::new(SystemClock::new());
1156 let policy = Policy::count_based(100).unwrap();
1157 let registry = SuppressionRegistry::new(storage, clock, policy);
1158 let config = EmitterConfig::new(Duration::from_millis(30)).unwrap();
1159
1160 let emitter = SummaryEmitter::new(registry, config);
1161 let handle = emitter.start(|_summaries| {}, false);
1162
1163 handle.shutdown().await.expect("shutdown should succeed");
1165
1166 tokio::time::sleep(Duration::from_millis(100)).await;
1168 }
1169}