feat: retry policies with exponential backoff and exception filtering#4949
feat: retry policies with exponential backoff and exception filtering#4949
Conversation
Code ReviewOverall this is a clean, well-structured feature addition. The API design is intuitive and the documentation is thorough. Two issues found: Issue 1: Public API snapshot tests not updated (CLAUDE.md Rule 2 violation)The PR adds several new public API members to New public members missing from all four
Per CLAUDE.md: "Changes to source generator output or public APIs require running snapshot tests. Commit Fix: Run the snapshot tests, review the generated # From TUnit.Core.SourceGenerator.Tests or the relevant test project:
# Linux/macOS:
for f in *.received.txt; do mv "$f" "${f%.received.txt}.verified.txt"; done
# Then commit the updated .verified.txt filesIssue 2: Integer overflow in exponential backoff delay calculationIn var delayMs = (int)(backoffMs * Math.Pow(multiplier, attempt));
if (delayMs > 0)
{
await Task.Delay(delayMs, testContext.CancellationToken).ConfigureAwait(false);
}
With While attempt 22 is extreme, smaller Recommended fix — cap the computed delay to avoid the overflow: private static async Task ApplyBackoffDelay(TestContext testContext, int attempt)
{
var backoffMs = testContext.Metadata.TestDetails.RetryBackoffMs;
if (backoffMs <= 0)
{
return;
}
var multiplier = testContext.Metadata.TestDetails.RetryBackoffMultiplier;
var rawDelayMs = backoffMs * Math.Pow(multiplier, attempt);
// Cap to int.MaxValue to avoid undefined behavior from unchecked cast
var delayMs = (int)Math.Min(rawDelayMs, int.MaxValue);
if (delayMs > 0)
{
await Task.Delay(delayMs, testContext.CancellationToken).ConfigureAwait(false);
}
}Or use var delay = TimeSpan.FromMilliseconds(rawDelayMs);
await Task.Delay(delay, testContext.CancellationToken).ConfigureAwait(false);( |
…filtering Enhance the existing [Retry] attribute with built-in retry policy support: - BackoffMs: initial delay between retries (default 0 = no delay) - BackoffMultiplier: multiplier for exponential backoff (default 2.0) - RetryOnExceptionTypes: only retry on specific exception types (Type[]) The backoff delay is applied in RetryHelper before each retry attempt, growing exponentially as BackoffMs * BackoffMultiplier^attempt. The exception filtering is integrated into the base ShouldRetry method, preserving full backward compatibility with existing custom subclasses.
fd1d720 to
68cad14
Compare
* fix: update Core public API snapshots for all frameworks The merged PRs #4929 (hardcoded constants) and #4949 (retry policies) added new public API surface to TUnit.Core but the DotNet8_0, DotNet9_0, and DotNet10_0 verified snapshot files were not updated, causing Core_Library_Has_No_API_Changes test failures in all PR CI pipelines. * fix: update analyzer tests for CancellationToken parameter location PR #4956 changed the CancellationTokenMustBeLastParameter diagnostic to point at the CancellationToken parameter instead of the method name. Update 3 test expectations to match the new diagnostic location.
Closes #4890. Adds BackoffMs, BackoffMultiplier, RetryOnExceptionTypes to [Retry].