Repository navigation
[Mocha] Fix in-process retry ignoring endpoint and transport exception policies - #10514
Conversation
…n policies ConsumerRetryMiddleware only read the bus-level ExceptionPolicyFeature, while ReceiveRedeliveryMiddleware resolved endpoint, then transport, then bus. Both now share ExceptionPolicyResolver. The consumer pipeline resolves the policy from the endpoints the consumer is bound to and fails at startup when those endpoints resolve to different policies.
There was a problem hiding this comment.
🟡 Changes recommended
The new policy precedence and conflict behavior lack regression tests.
1 open finding
What changed in this PR
Centralizes exception-policy resolution so in-process retries respect endpoint and transport policies.
Changes:
- Adds endpoint → transport → bus policy resolution.
- Detects conflicting policies across consumer endpoints.
- Reuses the resolver for redelivery and consumer retry.
| File | Description |
|---|---|
ThrowHelper.cs |
Adds the policy-conflict exception. |
ReceiveRedeliveryMiddleware.cs |
Uses the shared resolver. |
ExceptionPolicyResolver.cs |
Implements policy precedence. |
ConsumerRetryMiddleware.cs |
Resolves policies from bound endpoints. |
🧠 Review effort: Balanced
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Patch coverage100.0% of changed lines covered (37/37)
Project coverage: 58.6% (314223/536470 lines) |
| Assert.Contains("resolve to different exception policies", exception.Message); | ||
| Assert.Contains("'ep-a'", exception.Message); | ||
| Assert.Contains("'ep-b'", exception.Message); |
There was a problem hiding this comment.
I don't like these contains assertions the agent does. But it's taste i guess
There was a problem hiding this comment.
Me neither. If there's no exception code, I usually prefer matching the full message. If everyone agrees, we could consider adding it to the AGENTS.md file.

Summary
In-process retries (
ConsumerRetryMiddleware) only read the bus-levelExceptionPolicyFeature, while redelivery (ReceiveRedeliveryMiddleware) resolved endpoint, then transport, then bus. AnAddResilienceon an endpoint or transport therefore changed redelivery but not in-process retries:endpoint.AddResilience(p => p.On<InvalidOperationException>().Retry(3))without a bus policy ran the handler once.Retry()overridden by a transportDeadLetter()still ran the handler 4 times before dead-lettering.Changes
ExceptionPolicyResolverholds the endpoint, transport, bus precedence and is used by both middlewares.ThrowHelper.ConsumerExceptionPolicyConflict).Behavior change
Endpoints and transports with their own
AddResiliencenow get the in-process retries their policy configures. Previously those retries came from the bus policy, or did not happen when no bus policy was set.Testing
Mocha.Tests: 4264 passed.Mocha.Transport.InMemory.Tests: 900 passed.Mocha.Sagas.Tests: 610 passed, 4 skipped, 2 failed. Both failures areSaga_Should_TimeoutWithCustomResponse(net8.0, net9.0), which configures no exception policy and waits a fixed 500 ms. It passes 3 out of 3 runs when run on its own.RetryTestscount handler attempts:Retry_Should_RetryHandler_When_EndpointConfiguresRetry(endpointRetry(3)with no bus policy runs the handler 4 times) andRetry_Should_NotRetry_When_TransportPolicyDeadLetters(busRetry()overridden by transportDeadLetter()runs it once).Retry_Should_ThrowOnStartup_When_ConsumerEndpointsResolveToDifferentPoliciescovers the startup conflict. These compile but were not run locally.