@@ -158,7 +158,9 @@ async fn run(
158158
159159 // Note: This loop, or anything called within it, MUST NOT generate
160160 // any logs that don't break the loop, as that could cause an
161- // infinite loop since it receives all such logs.
161+ // infinite loop since it receives all such logs. Broadcast lag is
162+ // reported by `TraceSubscription::into_stream` itself, after an event
163+ // has been received, so its log cannot lag the receiver again.
162164 while let Some ( mut log) = rx. next ( ) . await {
163165 // TODO: Should this actually be in memory size?
164166 let byte_size = log. estimated_json_encoded_size_of ( ) . get ( ) ;
@@ -502,4 +504,99 @@ mod tests {
502504
503505 assert_eq ! ( definitions, Some ( expected_definition) )
504506 }
507+
508+ // Verify that broadcast lag is surfaced via `component_discarded_events_total` rather
509+ // than being silently swallowed.
510+ //
511+ // Strategy: run inside a single-threaded tokio runtime (the default for `#[tokio::test]`).
512+ // While the current task holds the CPU without yielding, no other tokio tasks are scheduled.
513+ // We flood the broadcast channel (capacity 99) with more events than it can hold. The source
514+ // task cannot poll between emits, so the broadcast overflows and records a lag count. After
515+ // we yield, the source observes the lag and emits `ComponentEventsDropped`.
516+ #[ tokio:: test]
517+ #[ serial]
518+ async fn broadcast_lag_increments_discarded_metric ( ) {
519+ trace:: init ( false , false , "error" , 10 , None ) ;
520+ vector_lib:: metrics:: init_test ( ) ;
521+ trace:: reset_early_buffer ( ) ;
522+
523+ // The downstream receiver is kept alive but never polled, so the source does not stop
524+ // on a closed output and no events are consumed downstream before we check the metric.
525+ let ( tx, _rx) = SourceSender :: new_test ( ) ;
526+ let source = InternalLogsConfig :: default ( )
527+ . build ( SourceContext :: new_test ( tx, None ) )
528+ . await
529+ . unwrap ( ) ;
530+ tokio:: spawn ( source) ;
531+
532+ // Yield so the source task subscribes and starts polling the broadcast, then stop early
533+ // buffering so new events go to the live broadcast.
534+ tokio:: task:: yield_now ( ) . await ;
535+ trace:: stop_early_buffering ( ) ;
536+ tokio:: task:: yield_now ( ) . await ;
537+
538+ let controller =
539+ vector_lib:: metrics:: Controller :: get ( ) . expect ( "metrics controller must be initialized" ) ;
540+ controller. reset ( ) ;
541+
542+ // Emit more events than the broadcast capacity (99) without yielding. In a
543+ // single-threaded runtime this guarantees the source task cannot poll between emits,
544+ // so the broadcast overflows and accumulates a lag count.
545+ for i in 0usize ..200 {
546+ error ! ( message = "Broadcast lag test." , i) ;
547+ }
548+
549+ // Yield enough times for the source task to observe the lag and emit
550+ // `ComponentEventsDropped`.
551+ for _ in 0 ..20 {
552+ tokio:: task:: yield_now ( ) . await ;
553+ }
554+
555+ let discarded_any = controller
556+ . capture_metrics ( )
557+ . into_iter ( )
558+ . any ( |m| m. name ( ) == "component_discarded_events_total" ) ;
559+
560+ assert ! (
561+ discarded_any,
562+ "expected component_discarded_events_total to be emitted when broadcast lags"
563+ ) ;
564+ }
565+
566+ // After a lag, the broadcast receiver points at the oldest slot of a full buffer. Any log
567+ // emitted before the next receive, such as the `ComponentEventsDropped` error, overwrites
568+ // that slot and lags the receiver again. Verify the source still delivers the newest event of
569+ // the burst instead of repeating that cycle.
570+ #[ tokio:: test]
571+ #[ serial]
572+ async fn recovers_after_broadcast_lag ( ) {
573+ trace:: init ( false , false , "error" , 10 , None ) ;
574+ trace:: reset_early_buffer ( ) ;
575+
576+ let mut rx = start_source ( ) . await ;
577+
578+ // Overflow the broadcast (capacity 99) without yielding, so the source lags on its next
579+ // receive. The last event is the newest one retained by the broadcast.
580+ for i in 0usize ..200 {
581+ error ! ( message = "Broadcast lag test." , i) ;
582+ }
583+ error ! ( message = "Last event after lag." ) ;
584+
585+ let last = Value :: from ( "Last event after lag." ) ;
586+ let delivered = tokio:: time:: timeout ( Duration :: from_secs ( 5 ) , async {
587+ while let Some ( event) = rx. next ( ) . await {
588+ if event. as_log ( ) . get ( event_path ! ( "message" ) ) == Some ( & last) {
589+ return true ;
590+ }
591+ }
592+ false
593+ } )
594+ . await
595+ . unwrap_or ( false ) ;
596+
597+ assert ! (
598+ delivered,
599+ "source did not deliver events after broadcast lag"
600+ ) ;
601+ }
505602}
0 commit comments