Skip to content

Make message consumers pausable through a restart-safe RestartableBackgroundService and ITransport.StopAsync, and run the outbox relay on the same base - #429

Merged
Vulthil merged 1 commit into
mainfrom
feature/restartable-message-consumers
Oct 2, 2026
Merged

Vulthil merged 1 commit into
mainfrom
feature/restartable-message-consumers

Conversation

@Vulthil

@Vulthil Vulthil commented Oct 2, 2026

Copy link
Copy Markdown
Owner

Summary

Message consumers can now be paused and resumed, the same way the outbox relay already could. This is the first of two PRs for one owner of the test-host scope: the test reset pauses every IRestartableHostedService, and before this change the RabbitMQ consumers never stopped (ITransport had only StartAsync, and the consumer host's loop ended once the transport had started). On a factory that uses RabbitMQ, a shared host and a per-test host read from the same per-class virtual host, so they compete for the same messages.

  • RestartableBackgroundService (new, public, Vulthil.Extensions.Hosting): a restart-safe base class used in place of BackgroundService. It is the outbox relay's start/stop code, moved: every start runs ExecuteAsync as a new generation, a stop cancels the generation and waits for it, a start first waits for the previous generation, and a fault is logged and stops the application. One gap is closed on the way: of two overlapping starts, only one now begins a generation (before, both could, and one generation ran with no owner).
  • OutboxBackgroundService derives from it. The relay behavior does not change; its lifecycle tests move to the base class.
  • ITransport.StopAsync (new, with a default that does nothing): stops consumption and waits for the deliveries being handled. A stopped transport can start again.
  • ConsumerHostedService derives from the base class and is now restartable. Each generation starts the transport (with the same retry loop as before), waits until it is stopped, then stops the transport. Because the next start waits for the previous generation, a restart never starts the transport while the previous stop still runs. A failed stop is logged and does not stop the application.
  • RabbitMqBus.StopAsync cancels every consumer, waits for running handlers, and closes the consumer channels; StartAsync then consumes again with new consumers. Worker disposal now disposes every worker even when one fails, and always empties the list, so a later start never keeps an old worker.
  • Vulthil.Messaging now references Vulthil.Extensions.Hosting.
  • Behavior change: at shutdown, consumers now stop when the host stops the consumer host. Before, they stopped only when the DI container disposed the bus.
  • Docs: hosting README and package page, the custom-transport guide (StopAsync), and testing.md (consumers pause during the reset).

No breaking change: every API change is an addition, so there are no compatibility suppressions.

Verification

  • dotnet build Vulthil.SharedKernel.slnx: 0 warnings, 0 errors.
  • All 18 unit test projects pass on net10.0 and net9.0. New: tests/Vulthil.Extensions.Hosting.Tests (13 tests, 8 of them moved from the relay tests), 4 more consumer-host tests, 3 RabbitMqBusStopTests.
  • Vulthil.IntegrationTests (Docker): 69/69 on net10.0 and net9.0, including the new RabbitMqConsumerPauseIntegrationTests on a real broker: while the consumer host is paused, a published message waits in its queue (ready count 1); after the resume it is delivered once.
  • Aspire MessagingIntegrationTests: 13/13 on net10.0.
  • dotnet pack package validation against 1.2.0 passes for Vulthil.Extensions.Hosting, Vulthil.Messaging, Vulthil.Messaging.RabbitMq and Vulthil.SharedKernel.Outbox.
  • Break checks (each made tests fail, then reverted):
    • no overlapping-start guard: 1 test fails;
    • a start that does not wait for the previous generation: 3 tests fail;
    • the consumer host never stops the transport: 3 unit tests and the real-broker test fail;
    • RabbitMqBus.StopAsync does nothing: 3 unit tests and the real-broker test fail;
    • the worker list is never emptied: 2 tests fail.

Backport to v1.0: no

…kgroundService and ITransport.StopAsync, and run the outbox relay on the same base
@claude

claude Bot commented Oct 2, 2026

Copy link
Copy Markdown

Code review

No issues found. Checked for bugs and CLAUDE.md compliance.

@Vulthil
Vulthil merged commit d5ce427 into main Oct 2, 2026
7 checks passed
@Vulthil
Vulthil deleted the feature/restartable-message-consumers branch October 2, 2026 11:12
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.

1 participant