Skip to content

GH-5068 DoThenRetry() error policy that works inline and from a queue - #5179

Merged
jeremydmiller merged 1 commit into
mainfrom
gh-5068-do-then-retry
Oct 23, 2026
Merged

jeremydmiller merged 1 commit into
mainfrom
gh-5068-do-then-retry

Conversation

@jeremydmiller

Copy link
Copy Markdown
Member

What changed

  • New DoThenRetry(action, maxAttempts = 3, description = "Do then retry") on IFailureActions / PolicyExpression, so it is available on global policies (opts.OnException<T>()), on a handler chain (chain.OnException<T>()), and after Then.
  • New internal DoThenRetryContinuationSource / DoThenRetryContinuation. The continuation implements both:
    • IContinuation: run the action, then lifecycle.RetryExecutionNowAsync() (queued path, with the same retry tracing as RetryInlineContinuation)
    • IInlineContinuation: run the action, then return InvokeResult.TryAgain (IMessageBus.InvokeAsync() path)
  • maxAttempts counts the original attempt, like Requeue(maxAttempts): it adds maxAttempts - 1 slots. Once they run out, the normal exhausted behaviour applies, the same as RetryTimes(): dead letter when queued, exception thrown to the InvokeAsync() caller. maxAttempts must be 2..25.
  • RetryExecutionNowAsync() inline behaviour is unchanged.
  • Docs: new "Do this, then retry" section in docs/guide/handlers/error-handling.md, sourced from the test's sample_do_then_retry region.

Why

A CustomAction that does recovery work and then retries can't know whether it is running inline or queued, so the same lambda either throws (RetryExecutionNowAsync() inline) or doesn't retry. Users had to write their own dual continuation plus an attempts check.

Tests run locally (net9.0, Release)

  • CoreTests --filter-class "*do_then_retry*": 6/6 passed (queued recover, inline recover, queued exhausted -> dead letter, inline exhausted -> throws, rule description/attempt slots with Then, argument validation)
  • CoreTests --filter-namespace "CoreTests.ErrorHandling*": 249/249 passed
  • src/Wolverine and CoreTests build with 0 warnings / 0 errors

Closes #5068

🤖 Generated with Claude Code

https://claude.ai/code/session_01DCggXgxnG3EBXoR9rrq37U

…, inline or queued

New OnException<T>().DoThenRetry(action, maxAttempts: 3) on IFailureActions/PolicyExpression
(global policies and per-chain OnException). The continuation implements both IContinuation
(action, then lifecycle.RetryExecutionNowAsync()) and IInlineContinuation (action, then
InvokeResult.TryAgain). maxAttempts counts the original attempt like Requeue(maxAttempts), so it
adds maxAttempts - 1 slots; when they run out the normal exhausted behaviour applies (dead letter
when queued, exception thrown to the InvokeAsync() caller). RetryExecutionNowAsync() is unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DCggXgxnG3EBXoR9rrq37U
@jeremydmiller
jeremydmiller merged commit 364b059 into main Oct 23, 2026
@jeremydmiller jeremydmiller mentioned this pull request Oct 23, 2026
This was referenced Oct 25, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

A 'do this, then retry' error policy that works both inline and from a queue

1 participant