Skip to content

[Mocha] Fix in-process retry ignoring endpoint and transport exception policies - #10514

Merged
PascalSenn merged 4 commits into
mainfrom
pse/mocha-retry-endpoint-exception-policy
Oct 9, 2026
Merged

PascalSenn merged 4 commits into
mainfrom
pse/mocha-retry-endpoint-exception-policy

Conversation

@PascalSenn

@PascalSenn PascalSenn commented Oct 8, 2026 •

Copy link
Copy Markdown
Member

Summary

In-process retries (ConsumerRetryMiddleware) only read the bus-level ExceptionPolicyFeature, while redelivery (ReceiveRedeliveryMiddleware) resolved endpoint, then transport, then bus. An AddResilience on 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.
  • A bus Retry() overridden by a transport DeadLetter() still ran the handler 4 times before dead-lettering.

Changes

  • ExceptionPolicyResolver holds the endpoint, transport, bus precedence and is used by both middlewares.
  • The consumer pipeline resolves the policy from the endpoints the consumer is bound to when it is compiled. It falls back to the bus policy when the consumer has no bound endpoint.
  • A consumer bound to endpoints that resolve to different exception policies fails at startup (ThrowHelper.ConsumerExceptionPolicyConflict).

Behavior change

Endpoints and transports with their own AddResilience now 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 are Saga_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.
  • RabbitMQ, PostgreSQL and Azure Service Bus transport tests were not run.
  • New behavior tests in RetryTests count handler attempts: Retry_Should_RetryHandler_When_EndpointConfiguresRetry (endpoint Retry(3) with no bus policy runs the handler 4 times) and Retry_Should_NotRetry_When_TransportPolicyDeadLetters (bus Retry() overridden by transport DeadLetter() runs it once). Retry_Should_ThrowOnStartup_When_ConsumerEndpointsResolveToDifferentPolicies covers the startup conflict. These compile but were not run locally.

…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.
Copilot AI balanced review requested due to automatic review settings October 8, 2026 20:12

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 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.

@github-actions

github-actions Bot commented Oct 8, 2026 •

Copy link
Copy Markdown
Contributor

Patch coverage

100.0% of changed lines covered (37/37)

File Covered Changed Patch %
…/Mocha/Middlewares/Consume/Retry/ConsumerRetryMiddleware.cs 24 24 100.0% 🟢
…/Mocha/Middlewares/Consume/Retry/ExceptionPolicyResolver.cs 9 9 100.0% 🟢
…/Receive/Redelivery/ReceiveRedeliveryMiddleware.cs 1 1 100.0% 🟢
src/Mocha/src/Mocha/ThrowHelper.cs 3 3 100.0% 🟢

Project coverage: 58.6% (314223/536470 lines)

@PascalSenn
PascalSenn merged commit 85f4b2b into main Oct 9, 2026
158 checks passed
@PascalSenn
PascalSenn deleted the pse/mocha-retry-endpoint-exception-policy branch October 9, 2026 14:03
Comment on lines +151 to +153
Assert.Contains("resolve to different exception policies", exception.Message);
Assert.Contains("'ep-a'", exception.Message);
Assert.Contains("'ep-b'", exception.Message);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't like these contains assertions the agent does. But it's taste i guess

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants