Skip to main content

tracing_throttle/application/
emitter.rs

1//! Summary emission for suppressed events.
2//!
3//! Periodically collects and emits summaries of suppressed events to provide
4//! visibility into what has been rate limited.
5
6use 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/// Error returned when emitter configuration validation fails.
20#[derive(Debug, Clone, PartialEq, Eq)]
21pub enum EmitterConfigError {
22    /// Summary interval duration must be greater than zero
23    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/// Error returned when emitter shutdown fails.
39#[cfg(feature = "async")]
40#[derive(Debug, Clone, PartialEq, Eq)]
41pub enum ShutdownError {
42    /// Task panicked during shutdown
43    TaskPanicked,
44    /// Task was cancelled before completing
45    TaskCancelled,
46    /// Shutdown exceeded the specified timeout
47    Timeout,
48    /// Failed to send shutdown signal (task may have already exited)
49    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/// Configuration for summary emission.
68#[derive(Debug, Clone)]
69pub struct EmitterConfig {
70    /// How often to emit summaries
71    pub interval: Duration,
72    /// Minimum suppression count to include in summary
73    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    /// Create a new emitter config with the specified interval.
87    ///
88    /// # Errors
89    /// Returns `EmitterConfigError::ZeroSummaryInterval` if `interval` is zero.
90    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    /// Set the minimum suppression count threshold.
101    pub fn with_min_count(mut self, min_count: usize) -> Self {
102        self.min_count = min_count;
103        self
104    }
105}
106
107/// Handle for controlling a running emitter task.
108///
109/// # Shutdown Behavior
110///
111/// You **must** call `shutdown().await` to stop the emitter task. The handle does
112/// not implement `Drop` to avoid race conditions and resource leaks.
113///
114/// If you drop the handle without calling `shutdown()`, the background task will
115/// continue running indefinitely, potentially causing:
116/// - Resource leaks (the task holds references to the registry)
117/// - Unexpected behavior if the task outlives expected lifetime
118/// - Inability to observe task failures or panics
119///
120/// # Examples
121///
122/// ```rust,no_run
123/// # use tracing_throttle::application::emitter::{SummaryEmitter, EmitterConfig};
124/// # use tracing_throttle::application::registry::SuppressionRegistry;
125/// # use tracing_throttle::domain::policy::Policy;
126/// # use tracing_throttle::infrastructure::storage::ShardedStorage;
127/// # use tracing_throttle::infrastructure::clock::SystemClock;
128/// # use std::sync::Arc;
129/// # async fn example() {
130/// # let storage = Arc::new(ShardedStorage::new());
131/// # let clock = Arc::new(SystemClock::new());
132/// # let policy = Policy::count_based(100).unwrap();
133/// # let registry = SuppressionRegistry::new(storage, clock, policy);
134/// # let config = EmitterConfig::default();
135/// # let emitter = SummaryEmitter::new(registry, config);
136/// let handle = emitter.start(|_| {}, false);
137///
138/// // Always call shutdown explicitly
139/// handle.shutdown().await.expect("shutdown failed");
140/// # }
141/// ```
142#[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    /// Trigger graceful shutdown and wait for the task to complete.
151    ///
152    /// This method uses a default timeout of 10 seconds. For custom timeout durations,
153    /// use [`shutdown_with_timeout`](Self::shutdown_with_timeout).
154    ///
155    /// # Errors
156    ///
157    /// Returns an error if:
158    /// - The task panics during shutdown
159    /// - The task is cancelled
160    /// - Shutdown exceeds the timeout (10 seconds)
161    /// - The shutdown signal fails to send
162    ///
163    /// # Examples
164    ///
165    /// ```no_run
166    /// # use tracing_throttle::application::emitter::{SummaryEmitter, EmitterConfig};
167    /// # use tracing_throttle::application::registry::SuppressionRegistry;
168    /// # use tracing_throttle::domain::policy::Policy;
169    /// # use tracing_throttle::infrastructure::storage::ShardedStorage;
170    /// # use tracing_throttle::infrastructure::clock::SystemClock;
171    /// # use std::sync::Arc;
172    /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
173    /// # let storage = Arc::new(ShardedStorage::new());
174    /// # let clock = Arc::new(SystemClock::new());
175    /// # let policy = Policy::count_based(100).unwrap();
176    /// # let registry = SuppressionRegistry::new(storage, clock, policy);
177    /// # let config = EmitterConfig::default();
178    /// # let emitter = SummaryEmitter::new(registry, config);
179    /// let handle = emitter.start(|_| {}, false);
180    ///
181    /// // Do some work...
182    ///
183    /// // Clean shutdown with default 10 second timeout
184    /// handle.shutdown().await?;
185    /// # Ok(())
186    /// # }
187    /// ```
188    pub async fn shutdown(self) -> Result<(), ShutdownError> {
189        self.shutdown_with_timeout(Duration::from_secs(10)).await
190    }
191
192    /// Trigger graceful shutdown with a custom timeout.
193    ///
194    /// This method:
195    /// 1. Sends the shutdown signal
196    /// 2. Waits for the background task to finish (up to the timeout)
197    /// 3. If the timeout is exceeded, the task is aborted
198    ///
199    /// # Errors
200    ///
201    /// Returns an error if:
202    /// - The task panics during shutdown
203    /// - The task is cancelled
204    /// - Shutdown exceeds the specified timeout
205    /// - The shutdown signal fails to send
206    ///
207    /// # Examples
208    ///
209    /// ```no_run
210    /// # use tracing_throttle::application::emitter::{SummaryEmitter, EmitterConfig};
211    /// # use tracing_throttle::application::registry::SuppressionRegistry;
212    /// # use tracing_throttle::domain::policy::Policy;
213    /// # use tracing_throttle::infrastructure::storage::ShardedStorage;
214    /// # use tracing_throttle::infrastructure::clock::SystemClock;
215    /// # use std::sync::Arc;
216    /// # use std::time::Duration;
217    /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
218    /// # let storage = Arc::new(ShardedStorage::new());
219    /// # let clock = Arc::new(SystemClock::new());
220    /// # let policy = Policy::count_based(100).unwrap();
221    /// # let registry = SuppressionRegistry::new(storage, clock, policy);
222    /// # let config = EmitterConfig::default();
223    /// # let emitter = SummaryEmitter::new(registry, config);
224    /// let handle = emitter.start(|_| {}, false);
225    ///
226    /// // Do some work...
227    ///
228    /// // Clean shutdown with 5 second timeout
229    /// handle.shutdown_with_timeout(Duration::from_secs(5)).await?;
230    /// # Ok(())
231    /// # }
232    /// ```
233    pub async fn shutdown_with_timeout(
234        mut self,
235        timeout_duration: Duration,
236    ) -> Result<(), ShutdownError> {
237        use tokio::time::timeout;
238
239        // Send shutdown signal
240        if self.shutdown_tx.send(true).is_err() {
241            return Err(ShutdownError::SignalFailed);
242        }
243
244        // Wait for task to complete with timeout
245        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), // Treat unknown errors as panics
251                Err(_) => Err(ShutdownError::Timeout),
252            }
253        } else {
254            Ok(())
255        }
256    }
257
258    /// Check if the emitter task is still running.
259    pub fn is_running(&self) -> bool {
260        self.join_handle.as_ref().is_some_and(|h| !h.is_finished())
261    }
262}
263
264/// Emits periodic summaries of suppressed events.
265pub 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    /// Create a new summary emitter.
288    pub fn new(registry: SuppressionRegistry<S>, config: EmitterConfig) -> Self {
289        Self { registry, config }
290    }
291
292    /// Collect suppression summaries for newly reported suppressions.
293    ///
294    /// Returns summaries for events that have accumulated at least `min_count`
295    /// suppressions since their previous emission.
296    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    /// Start emitting summaries periodically (async version).
370    ///
371    /// This spawns a background task that emits summaries at the configured interval.
372    /// The task will run until `shutdown()` is called on the returned `EmitterHandle`.
373    ///
374    /// # Graceful Shutdown
375    ///
376    /// When `shutdown()` is called on the `EmitterHandle`:
377    /// 1. The shutdown signal is prioritized over tick events
378    /// 2. The current emission completes if in progress
379    /// 3. If `emit_final` is true, one final emission occurs with current summaries
380    /// 4. The background task completes gracefully
381    ///
382    /// # Cancellation Safety
383    ///
384    /// The spawned task is designed to shut down cleanly:
385    /// - `collect_summaries()` atomically claims unreported suppressions
386    /// - Claimed suppressions are not emitted again on later ticks
387    /// - Panics in `emit_fn` are caught and don't abort the task
388    /// - The `emit_fn` closure should be cancellation-safe (avoid holding locks across `.await`)
389    ///
390    /// # Type Parameters
391    ///
392    /// * `F` - The emission function. Must be `Send + 'static` because it runs
393    ///   in a spawned task that may execute on any thread in the tokio runtime.
394    ///   The function receives ownership of the summaries vector.
395    ///
396    /// # Examples
397    ///
398    /// ```no_run
399    /// # use tracing_throttle::application::emitter::{SummaryEmitter, EmitterConfig};
400    /// # use tracing_throttle::application::registry::SuppressionRegistry;
401    /// # use tracing_throttle::domain::policy::Policy;
402    /// # use tracing_throttle::infrastructure::storage::ShardedStorage;
403    /// # use tracing_throttle::infrastructure::clock::SystemClock;
404    /// # use std::sync::Arc;
405    /// # use std::time::Duration;
406    /// # async fn example() {
407    /// # let storage = Arc::new(ShardedStorage::new());
408    /// # let clock = Arc::new(SystemClock::new());
409    /// # let policy = Policy::count_based(100).unwrap();
410    /// # let registry = SuppressionRegistry::new(storage, clock, policy);
411    /// # let config = EmitterConfig::default();
412    /// let emitter = SummaryEmitter::new(registry, config);
413    /// let handle = emitter.start(|summaries| {
414    ///     for summary in summaries {
415    ///         tracing::warn!("{}", summary.format_message());
416    ///     }
417    /// }, true);
418    ///
419    /// // Later, trigger graceful shutdown
420    /// handle.shutdown().await.expect("shutdown failed");
421    /// # }
422    /// ```
423    #[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            // Skip missed ticks to prevent backpressure if emissions are slow
434            ticker.set_missed_tick_behavior(tokio::time::MissedTickBehavior::Skip);
435
436            loop {
437                tokio::select! {
438                    // Prioritize shutdown signal to ensure fast shutdown
439                    biased;
440
441                    _ = shutdown_rx.changed() => {
442                        if *shutdown_rx.borrow_and_update() {
443                            // Emit final summaries if requested
444                            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                            // emit_fn panicked - claims were rolled back, continue running
458                            #[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    /// Get the emitter configuration.
473    pub fn config(&self) -> &EmitterConfig {
474        &self.config
475    }
476
477    /// Get a reference to the registry.
478    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        // Add some suppressed events
513        for i in 0..3 {
514            let sig = EventSignature::simple("INFO", &format!("Message {}", i));
515            registry.with_event_state(sig, |state, now| {
516                // Simulate some suppressions
517                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        // Verify counts
529        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        // Add event with low count (below threshold)
550        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        // Add event with high count (above threshold)
558        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        // Only the high-count event should be included
569        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        // Add a suppressed event
653        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        // Track emissions
661        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        // Wait for a couple of intervals
672        tokio::time::sleep(Duration::from_millis(250)).await;
673
674        handle.shutdown().await.expect("shutdown failed");
675
676        // Should emit the suppression once, then stay quiet until new suppressions arrive.
677        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        // Add a suppressed event so there's something to emit
709        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        // Let it run for a bit
727        tokio::time::sleep(Duration::from_millis(250)).await;
728
729        // Trigger graceful shutdown
730        handle.shutdown().await.expect("shutdown failed");
731
732        // Verify task is no longer running
733        let final_count = *emissions.lock().unwrap();
734        assert!(final_count >= 1);
735
736        // Wait a bit more to ensure task really stopped
737        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(); // Long interval
752
753        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, // Emit final summaries
763        );
764
765        // Wait for first tick to complete (interval's first tick is immediate)
766        tokio::time::sleep(Duration::from_millis(50)).await;
767
768        // Now add some suppressions after the first tick
769        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        // Shutdown before next interval (which is 60 seconds away)
777        tokio::time::sleep(Duration::from_millis(50)).await;
778        handle.shutdown().await.expect("shutdown failed");
779
780        // Should have emitted final summaries
781        let emission_list = emissions.lock().unwrap();
782        assert_eq!(emission_list.len(), 1);
783        assert_eq!(emission_list[0], 1); // 1 summary
784    }
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, // No final emission
807        );
808
809        // Wait for first tick (immediate, but no emissions since no suppressions yet)
810        tokio::time::sleep(Duration::from_millis(50)).await;
811
812        // Add some suppressions after first tick
813        let sig = EventSignature::simple("INFO", "Test event");
814        registry.with_event_state(sig, |state, now| {
815            state.counter.record_suppression(now);
816        });
817
818        // Shutdown immediately (before next 60-second interval)
819        tokio::time::sleep(Duration::from_millis(50)).await;
820        handle.shutdown().await.expect("shutdown failed");
821
822        // Should not have emitted anything (no final emission)
823        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        // Should be running
839        assert!(handle.is_running());
840
841        // Shutdown
842        handle.shutdown().await.expect("shutdown failed");
843
844        // Should no longer be running
845        tokio::time::sleep(Duration::from_millis(50)).await;
846        // Note: is_running() consumes self, so we can't check after shutdown
847    }
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        // Add suppressions
861        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                // Simulate slow emission
874                std::thread::sleep(Duration::from_millis(30));
875                *emissions_clone.lock().unwrap() += 1;
876            },
877            false,
878        );
879
880        // Let first emission start
881        tokio::time::sleep(Duration::from_millis(60)).await;
882
883        // Shutdown should wait for current emission to complete
884        handle.shutdown().await.expect("shutdown failed");
885
886        // Current emission should have completed
887        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        // Let it run briefly
903        tokio::time::sleep(Duration::from_millis(150)).await;
904
905        // Shutdown with custom timeout should succeed quickly
906        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        // Add suppressions
923        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                // Panic on first call, succeed on subsequent calls
944                if count == 0 {
945                    panic!("intentional panic for testing");
946                }
947                // If we get here, panic was handled and task continued
948            },
949            false,
950        );
951
952        // The first emission panics and rolls back; the next tick retries the same batch.
953        tokio::time::sleep(Duration::from_millis(150)).await;
954
955        handle.shutdown().await.expect("shutdown failed");
956
957        // Should have attempted multiple emissions (first panicked, others succeeded)
958        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        // Should have attempted multiple times despite continuous panics
1015        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(); // Long interval
1033
1034        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, // emit_on_shutdown
1050        );
1051
1052        // Shutdown immediately - will trigger final emission which panics
1053        handle
1054            .shutdown()
1055            .await
1056            .expect("shutdown should succeed even if final emission panics");
1057
1058        // Verify final emission was attempted
1059        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                // Simulate slow emission - use a shorter duration for testing
1079                std::thread::sleep(Duration::from_millis(500));
1080            },
1081            true,
1082        );
1083
1084        // Shutdown with very short timeout
1085        let result = handle
1086            .shutdown_with_timeout(Duration::from_millis(10))
1087            .await;
1088
1089        // Should timeout (or at minimum not panic)
1090        // Note: timing-sensitive test, may occasionally pass if emit completes quickly
1091        let _ = result; // Don't assert - just verify no panic
1092    }
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 without calling shutdown
1108        drop(handle);
1109
1110        // Task should continue running
1111        // This is documented behavior - not a bug
1112        tokio::time::sleep(Duration::from_millis(100)).await;
1113
1114        // No assertions - just verify no panic
1115    }
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        // Create multiple shutdown senders
1130        let sender = handle.shutdown_tx.clone();
1131        let mut handles_vec = vec![];
1132
1133        // Multiple tasks try to send shutdown signal concurrently
1134        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        // All send operations should complete without panic
1142        for h in handles_vec {
1143            let _ = h.await;
1144        }
1145
1146        // Clean shutdown
1147        let _ = handle.shutdown().await;
1148    }
1149
1150    #[cfg(feature = "async")]
1151    #[tokio::test]
1152    async fn test_shutdown_signal_failure() {
1153        // Test that shutdown handles channel errors gracefully
1154        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        // Normal shutdown
1164        handle.shutdown().await.expect("shutdown should succeed");
1165
1166        // Verify task stopped
1167        tokio::time::sleep(Duration::from_millis(100)).await;
1168    }
1169}