@@ -91,6 +91,11 @@ type Processor struct {
9191 wg sync.WaitGroup
9292 isShuttingDown atomic.Bool
9393 shutdownOnce sync.Once
94+
95+ // dropCounts accumulates non-dispatched request outcomes between periodic summary flushes.
96+ // Written by both the main Run goroutine (enqueue, shutdown) and the runCleanupSweep goroutine (sweep),
97+ // so each slot is an atomic to avoid a data race.
98+ dropCounts [types .NumQueueOutcomes ]atomic.Uint64
9499}
95100
96101// NewProcessor creates a new Processor instance.
@@ -263,6 +268,7 @@ func (p *Processor) enqueue(item *FlowItem) {
263268 if finalState := outcome ; finalState != nil {
264269 p .logger .V (logutil .TRACE ).Info ("Item finalized externally before processing, discarding." ,
265270 "outcome" , finalState .Outcome , "err" , finalState .Err , "flowKey" , key , "requestID" , req .ID ())
271+ p .recordDrop (finalState .Outcome )
266272 return
267273 }
268274
@@ -272,6 +278,7 @@ func (p *Processor) enqueue(item *FlowItem) {
272278 finalErr := fmt .Errorf ("configuration error: failed to get queue for flow key %s: %w" , key , err )
273279 p .logger .Error (finalErr , "Rejecting request, queue lookup failed" , "flowKey" , key , "requestID" , req .ID ())
274280 item .FinalizeWithOutcome (types .QueueOutcomeRejectedOther , fmt .Errorf ("%w: %w" , types .ErrRejected , finalErr ))
281+ p .recordDrop (types .QueueOutcomeRejectedOther )
275282 return
276283 }
277284
@@ -280,6 +287,7 @@ func (p *Processor) enqueue(item *FlowItem) {
280287 finalErr := fmt .Errorf ("configuration error: failed to get priority band for priority %d: %w" , key .Priority , err )
281288 p .logger .Error (finalErr , "Rejecting request, priority band lookup failed" , "flowKey" , key , "requestID" , req .ID ())
282289 item .FinalizeWithOutcome (types .QueueOutcomeRejectedOther , fmt .Errorf ("%w: %w" , types .ErrRejected , finalErr ))
290+ p .recordDrop (types .QueueOutcomeRejectedOther )
283291 return
284292 }
285293
@@ -290,9 +298,12 @@ func (p *Processor) enqueue(item *FlowItem) {
290298 // that state reflects genuine unavailability (surfaced as 503), not backpressure against a contended pool (429).
291299 if p .poolEmpty {
292300 p .logger .V (logutil .DEBUG ).Info ("Rejecting request, queue at capacity with no endpoints" ,
293- "flowKey" , key , "requestID" , req .ID (), "reqByteSize" , req .ByteSize ())
301+ "flowKey" , key , "requestID" , req .ID (), "reqByteSize" , req .ByteSize (),
302+ "totalLen" , stats .TotalLen , "totalCapacityRequests" , stats .TotalCapacityRequests ,
303+ "totalByteSize" , stats .TotalByteSize , "totalCapacityBytes" , stats .TotalCapacityBytes )
294304 item .FinalizeWithOutcome (types .QueueOutcomeRejectedNoEndpoints , fmt .Errorf ("%w: %w" ,
295305 types .ErrRejected , types .ErrNoEndpoints ))
306+ p .recordDrop (types .QueueOutcomeRejectedNoEndpoints )
296307 return
297308 }
298309 p .logger .V (logutil .DEBUG ).Info ("Rejecting request, queue at capacity" ,
@@ -301,6 +312,7 @@ func (p *Processor) enqueue(item *FlowItem) {
301312 "totalByteSize" , stats .TotalByteSize , "totalCapacityBytes" , stats .TotalCapacityBytes )
302313 item .FinalizeWithOutcome (types .QueueOutcomeRejectedCapacity , fmt .Errorf ("%w: %w" ,
303314 types .ErrRejected , types .ErrQueueAtCapacity ))
315+ p .recordDrop (types .QueueOutcomeRejectedCapacity )
304316 return
305317 }
306318
@@ -311,6 +323,7 @@ func (p *Processor) enqueue(item *FlowItem) {
311323 p .logger .Error (finalErr , "Rejecting request, queue add failed" ,
312324 "flowKey" , key , "requestID" , req .ID ())
313325 item .FinalizeWithOutcome (types .QueueOutcomeRejectedOther , fmt .Errorf ("%w: %w" , types .ErrRejected , finalErr ))
326+ p .recordDrop (types .QueueOutcomeRejectedOther )
314327 return
315328 }
316329 p .logger .V (logutil .TRACE ).Info ("Item enqueued." ,
@@ -446,7 +459,7 @@ func (p *Processor) dispatchItem(itemAcc flowcontrol.QueueItemAccessor) error {
446459 // This happens benignly if the item was already removed by the cleanup sweep loop.
447460 // We log it at a low level for visibility but return nil so the dispatch cycle proceeds.
448461 p .logger .V (logutil .DEBUG ).Info ("Failed to remove item during dispatch (likely already finalized and swept)." ,
449- "flowKey" , key , "requestID" , req .ID (), "error " , err )
462+ "flowKey" , key , "requestID" , req .ID (), "err " , err )
450463 return nil
451464 }
452465
@@ -473,6 +486,7 @@ func (p *Processor) runCleanupSweep(ctx context.Context) {
473486 return
474487 case <- ticker .C ():
475488 p .sweepFinalizedItems ()
489+ p .flushDropSummary ()
476490 }
477491 }
478492}
@@ -486,6 +500,11 @@ func (p *Processor) sweepFinalizedItems() {
486500 }
487501 removedItems := managedQ .Cleanup (predicate )
488502 if len (removedItems ) > 0 {
503+ for _ , itemAcc := range removedItems {
504+ if fi , ok := itemAcc .(* FlowItem ); ok && fi != nil && fi .FinalState () != nil {
505+ p .recordDrop (fi .FinalState ().Outcome )
506+ }
507+ }
489508 logger .V (logutil .TRACE ).Info ("Swept finalized items and released capacity." ,
490509 "count" , len (removedItems ))
491510 }
@@ -510,13 +529,15 @@ func (p *Processor) shutdown() {
510529 // Finalize buffered items.
511530 item .FinalizeWithOutcome (types .QueueOutcomeRejectedOther ,
512531 fmt .Errorf ("%w: %w" , types .ErrRejected , types .ErrFlowControllerNotRunning ))
532+ p .recordDrop (types .QueueOutcomeRejectedOther )
513533 default :
514534 break DrainLoop
515535 }
516536 }
517537 // We do not close enqueueChan because external goroutines (Controller) send on it.
518538 // The channel will be garbage collected when the processor terminates.
519539 p .evictAll ()
540+ p .flushDropSummary ()
520541 })
521542}
522543
@@ -539,11 +560,36 @@ func (p *Processor) evictAll() {
539560 // Finalization is idempotent; safe to call even if already finalized externally.
540561 // The per-request log is emitted by EnqueueAndWait when it unblocks.
541562 item .FinalizeWithOutcome (outcome , errShutdown )
563+ p .recordDrop (item .FinalState ().Outcome )
542564 }
543565 }
544566 p .processAllQueuesConcurrently ("evictAll" , processFn )
545567}
546568
569+ func (p * Processor ) recordDrop (outcome types.QueueOutcome ) {
570+ if outcome == types .QueueOutcomeDispatched || outcome == types .QueueOutcomeNotYetFinalized {
571+ return
572+ }
573+ p .dropCounts [outcome ].Add (1 )
574+ }
575+
576+ func (p * Processor ) flushDropSummary () {
577+ var total uint64
578+ counts := make (map [string ]uint64 )
579+ for i := range p .dropCounts {
580+ if c := p .dropCounts [i ].Swap (0 ); c > 0 {
581+ total += c
582+ counts [types .QueueOutcome (i ).String ()] = c
583+ }
584+ }
585+ if total > 0 {
586+ p .logger .V (logutil .DEFAULT ).Info ("Flow control request drop summary" ,
587+ "poolName" , p .poolName ,
588+ "totalDropped" , total ,
589+ "counts" , counts )
590+ }
591+ }
592+
547593// processAllQueuesConcurrently iterates over all queues in all priority bands and executes the given
548594// `processFn` for each queue using a dynamically sized worker pool.
549595func (p * Processor ) processAllQueuesConcurrently (
0 commit comments