Concurrency
High High confidencesrc/HookRelay/Dispatch/WebhookDispatcher.cs` line 91 · @@ -74,6 +82,24 @@
`_attemptCount` is an instance field on `WebhookDispatcher`, which is registered as a singleton. Two webhooks being delivered concurrently share the same counter, so a delivery can be abandoned after zero retries because a different delivery already consumed the attempts. Under load this silently drops events — the exact scenario the PR is meant to fix.
`_attemptCount` is shared across all concurrent deliveries because the dispatcher is a singleton. A burst of failures on one endpoint will exhaust the counter for every in-flight delivery, so some webhooks get zero retries. Consider tracking the attempt count per delivery — e.g. a local variable in `DispatchAsync` or a field on `DeliveryAttempt` — instead of dispatcher state.
Context from PR ** Exact Match, High confidence
@@ -74,6 +82,24 @@
+ private int _attemptCount;
+
+ public async Task<DeliveryAttempt> DispatchAsync(WebhookEvent evt, CancellationToken ct)
+ {
+ while (_attemptCount < _options.MaxAttempts)
+ {
+ _attemptCount++;
+ var result = await SendAsync(evt, ct);
+ if (result.IsSuccess)
+ {
+ return DeliveryAttempt.Succeeded(evt, _attemptCount);
+ }
+
+ await Task.Delay(RetryPolicy.NextDelay(_attemptCount), ct);
+ }
+
+ return DeliveryAttempt.Abandoned(evt, _attemptCount);
+ } Why this context matters: The field declaration and the loop that mutates it are both visible in the diff, confirming the counter lives on the singleton dispatcher rather than on the per-delivery call path.