Files
UA-.NETStandard/Stack
Marc Schier 39eb086443 ChannelEntry: track tightest IRetryBudget across coalesced joiners; ClampDelayToBudget signals exhaustion
Two related correctness fixes for the channel-reconnect path.

1. ChannelEntry.RequestReconnectAsync coalescing budget
   When multiple callers concurrently invoke RequestReconnectAsync, the
   first becomes the "starter" and its budget is passed to the cycle.
   Subsequent "joiners" were silently dropping their budget parameter
   — so a joiner with a strict 10-second deadline could not tighten an
   in-flight cycle that the starter began without one.
   Fix: add an m_effectiveBudget field guarded by m_lock. Every caller
   (starter + joiners) updates it via TighterOf(current, candidate),
   which returns the budget with smaller remaining time (null = "no
   constraint", so a null joiner never relaxes). The cycle re-reads
   GetEffectiveBudget() at the top of every iteration AND immediately
   after the inter-attempt delay, so a late joiner can preempt an
   in-flight sleep. The finally block clears m_effectiveBudget so the
   next cycle starts unconstrained.
   XML doc on RequestReconnectAsync(CancellationToken) describes the
   coalescing semantics in plain language.

2. ChannelReconnectPolicyBudget.ClampDelayToBudget exhaustion semantics
   For a custom IChannelReconnectPolicy that does NOT also implement
   IBudgetAwareChannelReconnectPolicy, when the shared retry budget
   was exhausted the helper returned the ORIGINAL delay unchanged
   instead of signaling "stop retrying". Downstream
   ChannelEntry.RunReconnectCycleAsync independently checks
   budget.IsExhausted so no extra reconnect attempt was issued, but
   the bug wasted one delay cycle (up to MaxDelay = 30 s default)
   before the fault transition was detected.
   Fix: return Timeout.InfiniteTimeSpan when TryConsume returns false
   — matching ExponentialBackoffChannelReconnectPolicy.GetDelay.
2026-06-11 09:55:59 +02:00
..