Skip to content

[Mocha] Cancel message handlers and release interrupted messages on shutdown - #10504

Merged
tobias-tengler merged 10 commits into
mainfrom
mocha/shutdown-handler-cancellation
Oct 13, 2026
Merged

tobias-tengler merged 10 commits into
mainfrom
mocha/shutdown-handler-cancellation

Conversation

@tobias-tengler

@tobias-tengler tobias-tengler commented Oct 7, 2026 •

Copy link
Copy Markdown
Member

When a receive endpoint stopped, in-flight handlers were never cancelled because ReceiveContext discarded the transport's cancellation token, so they kept running after shutdown began. Stopping also left PostgreSQL messages behind. A handler that finished after the stop began could not delete its message, because the delete used the already cancelled token, and messages that were leased but not finished stayed locked until the 5 minute lease timeout. Both were then redelivered and processed a second time. Finally, a transport or endpoint that failed to stop kept the remaining ones from stopping.

This PR passes the endpoint's cancellation token to handlers, including batch handlers, and keeps shutdown cancellation from triggering retries, fault handling or error logs. RabbitMQ and Azure Service Bus settle messages with CancellationToken.None, so an interrupted message is still nacked or abandoned. Batches are collected and processed per receive endpoint, so stopping one endpoint only cancels its own batches, and MaxConcurrentBatches now applies per endpoint.

The PostgreSQL endpoint deletes or releases a handled message with CancellationToken.None. A delete that fails after the handler completed is logged and the message is released with the error recorded. When polling is cancelled, the endpoint releases the messages of the current batch that it still holds and undoes their delivery count bump, so they are redelivered right away instead of after the lease timeout, and the interrupted attempt does not count toward max delivery.

MessagingRuntime.StopAsync and MessagingTransport.StopAsync keep stopping the remaining transports and endpoints when one fails, then rethrow the first failure. A transport that failed to stop stays started, so disposing the runtime tries it again. Disposal runs every remaining step when one fails and rethrows the first failure once everything is released, except a stop failure that an earlier stop already reported.

@tobias-tengler
tobias-tengler marked this pull request as draft October 7, 2026 20:00
@tobias-tengler
tobias-tengler force-pushed the mocha/shutdown-handler-cancellation branch 2 times, most recently from 171c463 to 2fa5296 Compare October 7, 2026 20:06
@tobias-tengler
tobias-tengler force-pushed the mocha/shutdown-minimal branch from 750e258 to 6167eff Compare October 7, 2026 20:06
@github-actions

github-actions Bot commented Oct 7, 2026 •

Copy link
Copy Markdown
Contributor

Patch coverage

85.1% of changed lines covered (240/282)

File Covered Changed Patch %
…/Middlewares/Receive/RabbitMQAcknowledgementMiddleware.cs 1 2 50.0% 🔴
src/Mocha/src/Mocha/Runtime/MessagingRuntime.cs 21 32 65.6% 🔴
src/Mocha/src/Mocha/Transport/MessagingTransport.cs 16 23 69.6% 🔴
…/src/Mocha.Transport.Postgres/PostgresReceiveEndpoint.cs 46 57 80.7% 🟡
…/Mocha/src/Mocha/Consumers/Implementations/BatchConsumer.cs 64 75 85.3% 🟡
src/Mocha/src/Mocha/Consumers/Batching/BatchCollector.cs 15 16 93.8% 🟡
…/AzureServiceBusReceiveEndpoint.cs 3 3 100.0% 🟢
…/Receive/AzureServiceBusAcknowledgementMiddleware.cs 2 2 100.0% 🟢
…/src/Mocha.Transport.InMemory/InMemoryReceiveEndpoint.cs 3 3 100.0% 🟢
…/Mocha.Transport.Postgres/Connection/PostgresMessageStore.cs 25 25 100.0% 🟢
…/Connection/RabbitMQConsumerManager.cs 1 1 100.0% 🟢
…/src/Mocha.Transport.RabbitMQ/RabbitMQReceiveEndpoint.cs 17 17 100.0% 🟢
…/Mocha/src/Mocha/Consumers/Batching/MessageBatchExtensions.cs 18 18 100.0% 🟢
src/Mocha/src/Mocha/Endpoints/ReceiveEndpoint.cs 2 2 100.0% 🟢
…/Mocha/src/Mocha/Middlewares/Consume/Retry/RetryExecutor.cs 2 2 100.0% 🟢
…/src/Mocha/Middlewares/Receive/ReceiveDeadLetterMiddleware.cs 1 1 100.0% 🟢
…/src/Mocha/Middlewares/Receive/ReceiveFaultMiddleware.cs 1 1 100.0% 🟢
…/Receive/Redelivery/ReceiveRedeliveryMiddleware.cs 1 1 100.0% 🟢
src/Mocha/src/Mocha/Middlewares/ReceiveContext.cs 1 1 100.0% 🟢
Uncovered changed lines (JSON)
{
  "sha": "6063f29b49b7fcc28de6e120be3387cce4dfab91",
  "files": [
    { "path": "src/Mocha/src/Mocha.Transport.RabbitMQ/Middlewares/Receive/RabbitMQAcknowledgementMiddleware.cs", "ranges": [[38, 38]] },
    { "path": "src/Mocha/src/Mocha/Runtime/MessagingRuntime.cs", "ranges": [[202, 208], [228, 231]] },
    { "path": "src/Mocha/src/Mocha/Transport/MessagingTransport.cs", "ranges": [[251, 254], [272, 274]] },
    { "path": "src/Mocha/src/Mocha.Transport.Postgres/PostgresReceiveEndpoint.cs", "ranges": [[209, 212], [240, 243], [261, 263]] },
    { "path": "src/Mocha/src/Mocha/Consumers/Implementations/BatchConsumer.cs", "ranges": [[91, 93], [240, 241], [244, 248], [250, 250]] },
    { "path": "src/Mocha/src/Mocha/Consumers/Batching/BatchCollector.cs", "ranges": [[85, 85]] }
  ]
}

Project coverage: 58.7% (315704/538143 lines)

@tobias-tengler
tobias-tengler force-pushed the mocha/shutdown-handler-cancellation branch from 2fa5296 to b84e810 Compare October 8, 2026 07:05
@tobias-tengler
tobias-tengler force-pushed the mocha/shutdown-minimal branch 2 times, most recently from 8bc09cf to 7bb10f2 Compare October 8, 2026 07:08
@tobias-tengler
tobias-tengler force-pushed the mocha/shutdown-handler-cancellation branch 3 times, most recently from f616eee to 57f2418 Compare October 8, 2026 08:39
@tobias-tengler tobias-tengler changed the title [Mocha] Cancel message handlers on shutdown [Mocha] Cancel message handlers and release interrupted messages on shutdown Oct 8, 2026
@tobias-tengler
tobias-tengler force-pushed the mocha/shutdown-handler-cancellation branch 2 times, most recently from 7a16145 to 85ffdaf Compare October 8, 2026 13:30
@tobias-tengler
tobias-tengler force-pushed the mocha/shutdown-minimal branch from aae6435 to 13d6f71 Compare October 8, 2026 13:30
@tobias-tengler
tobias-tengler force-pushed the mocha/shutdown-handler-cancellation branch 2 times, most recently from 29d485e to 7a28857 Compare October 12, 2026 14:48
@tobias-tengler
tobias-tengler marked this pull request as ready for review October 12, 2026 16:49
Copilot AI balanced review requested due to automatic review settings October 12, 2026 16:50

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

PostgreSQL lease release retains last_delivered, preventing the promised immediate redelivery.

1 open finding
What changed in this PR

This PR improves Mocha shutdown behavior by canceling in-flight handlers, safely settling interrupted messages, and making lifecycle cleanup resilient to individual failures.

Changes:

  • Propagates shutdown cancellation through individual and batch handlers.
  • Releases or requeues interrupted transport messages.
  • Continues stopping and disposing remaining resources after failures.
File Description
src/​Mocha/​test/​Mocha.Transport.RabbitMQ.Tests/​Behaviors/​ShutdownTests.cs Tests cancellation and requeue on shutdown.
src/​Mocha/​test/​Mocha.Transport.Postgres.Tests/​Behaviors/​ShutdownTests.cs Tests PostgreSQL shutdown settlement.
src/​Mocha/​test/​Mocha.Transport.AzureServiceBus.Tests/​ReceiveEndpointLifecycleUnitTests.cs Tests endpoint failures and cancellation.
src/​Mocha/​test/​Mocha.Transport.AzureServiceBus.Tests/​Helpers/​FakeServiceBusClient.cs Adds message-processing test hooks.
src/​Mocha/​test/​Mocha.Transport.AzureServiceBus.Tests/​AzureServiceBusAcknowledgementMiddlewareTests.cs Verifies uncancelled settlement tokens.
src/​Mocha/​test/​Mocha.Tests/​Runtime/​MessagingRuntimeTests.cs Tests resilient runtime disposal.
src/​Mocha/​test/​Mocha.Tests/​Runtime/​MessagingRuntimeShutdownTests.cs Tests handler and batch cancellation.
src/​Mocha/​test/​Mocha.Tests/​Middlewares/​Consume/​Retry/​RetryExecutorTests.cs Tests cancellation during retries.
src/​Mocha/​test/​Mocha.Tests/​Consumers/​Batching/​BatchCollectorTests.cs Tests buffered-entry removal.
src/​Mocha/​src/​Mocha/​Transport/​MessagingTransport.cs Continues stopping endpoints after failures.
src/​Mocha/​src/​Mocha/​Runtime/​MessagingRuntime.cs Makes stop and disposal failure-resilient.
src/​Mocha/​src/​Mocha/​Middlewares/​ReceiveContext.cs Retains the receive cancellation token.
src/​Mocha/​src/​Mocha/​Middlewares/​Receive/​Redelivery/​ReceiveRedeliveryMiddleware.cs Skips redelivery handling during cancellation.
src/​Mocha/​src/​Mocha/​Middlewares/​Receive/​ReceiveFaultMiddleware.cs Skips fault generation during cancellation.
src/​Mocha/​src/​Mocha/​Middlewares/​Receive/​ReceiveDeadLetterMiddleware.cs Suppresses cancellation error logging.
src/​Mocha/​src/​Mocha/​Middlewares/​Consume/​Retry/​RetryExecutor.cs Stops retry processing when cancelled.
src/​Mocha/​src/​Mocha/​Endpoints/​ReceiveEndpoint.cs Propagates cancellation without critical logging.
src/​Mocha/​src/​Mocha/​Consumers/​Implementations/​BatchConsumer.cs Isolates batching per receive endpoint.
src/​Mocha/​src/​Mocha/​Consumers/​Batching/​BatchOptions.cs Documents per-endpoint concurrency.
src/​Mocha/​src/​Mocha/​Consumers/​Batching/​BatchCollector.cs Supports removing buffered entries.
src/​Mocha/​src/​Mocha.Transport.RabbitMQ/​RabbitMQReceiveEndpoint.cs Handles shutdown cancellation.
src/​Mocha/​src/​Mocha.Transport.RabbitMQ/​Middlewares/​Receive/​RabbitMQAcknowledgementMiddleware.cs Settles messages despite cancellation.
src/​Mocha/​src/​Mocha.Transport.RabbitMQ/​Connection/​RabbitMQConsumerManager.cs Cancels handlers before disconnecting.
src/​Mocha/​src/​Mocha.Transport.Postgres/​PostgresReceiveEndpoint.cs Deletes or releases interrupted messages.
src/​Mocha/​src/​Mocha.Transport.Postgres/​Connection/​PostgresMessageStore.cs Adds bulk lease release support.
src/​Mocha/​src/​Mocha.Transport.InMemory/​InMemoryReceiveEndpoint.cs Handles expected shutdown cancellation.
src/​Mocha/​src/​Mocha.Transport.AzureServiceBus/​Middlewares/​Receive/​AzureServiceBusAcknowledgementMiddleware.cs Settles messages independently of cancellation.
src/​Mocha/​src/​Mocha.Transport.AzureServiceBus/​AzureServiceBusReceiveEndpoint.cs Handles cancelled processor callbacks.

🧠 Review effort: Balanced


💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

@tobias-tengler
tobias-tengler removed this pull request from stack #10505 October 13, 2026 06:41
Base automatically changed from mocha/shutdown-minimal to main October 13, 2026 06:41
… failures

Delete or release handled Postgres messages with a token that stop does not cancel, and release the rest of a batch when polling is cancelled, so in-flight messages are not redelivered after the lock timeout.

Keep stopping the remaining transports and endpoints when one fails, then rethrow the first failure.
The shutdown tests asserted that stopping produced no warning or error
logs through a LogRecorder test helper. They now assert only the handler
cancellation and the resulting database state, and the helper is removed.
This was referenced Oct 28, 2026
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.

3 participants