Connection Lifecycle
This page explains what happens at three critical points in a CarrotMQ service's lifetime: initial startup, mid-run connection loss, and controlled shutdown. Understanding this behaviour is essential for operating a service reliably in production.
Initial Connection
When the hosted service starts, CarrotMQ calls BrokerConnection.ConnectAsync(), which enters a retry loop governed by InitialConnectionTimeout.
How the retry loop works:
- CarrotMQ attempts
ConnectionFactory.CreateConnectionAsync(). - On
BrokerUnreachableExceptionit logs the error and checks whetherInitialConnectionTimeouthas elapsed. - If not elapsed, it waits
NetworkRecoveryIntervalthen retries. - If the timeout has elapsed, it re-throws
BrokerUnreachableExceptionand the hosted service fails to start.
Default behaviour — InfiniteTimeSpan
The default value of InitialConnectionTimeout is Timeout.InfiniteTimeSpan. This means the service blocks indefinitely until RabbitMQ becomes available. This is the right default for container orchestration (Docker Compose, Kubernetes) where your service and RabbitMQ may start in parallel:
// appsettings.json — no override needed; InfiniteTimeSpan is the default
{
"BrokerConnection": {
"BrokerEndPoints": ["amqp://rabbitmq:5672"]
}
}
Finite timeout
Set a finite timeout when you want the .NET host to fault immediately if the broker is unavailable at startup rather than waiting forever:
{
"BrokerConnection": {
"BrokerEndPoints": ["amqp://rabbitmq:5672"],
"InitialConnectionTimeout": "00:00:30"
}
}
Or in code:
builder.ConfigureBrokerConnection(options =>
{
options.InitialConnectionTimeout = TimeSpan.FromSeconds(30);
});
When the timeout expires, BrokerUnreachableException is thrown. The .NET host treats this as a fatal startup error and terminates.
Tip: Prefer
InfiniteTimeSpanin orchestrated environments and a finite timeout in development so that misconfigured endpoints surface quickly.
Runtime Connection Loss & Automatic Recovery
Connection-level recovery
CarrotMQ uses the RabbitMQ.Client built-in automatic recovery. When the TCP connection is lost, the client library:
- Detects the outage.
- Reconnects to a broker node (honouring
BrokerEndPointsandRandomizeEndPointResolving). - Re-declares topology (exchanges, queues, bindings) from its in-memory snapshot.
- Re-registers all active consumers.
This happens transparently — no CarrotMQ code is involved. A Connection shut down warning is logged when the connection drops, and AutoRecovering connection succeeded is logged when recovery completes.
Channel-level recovery (AMQP errors)
AMQP errors with a reply code ≥ 400 close the channel without closing the connection. CarrotMQ's CarrotChannel layer detects these and runs its own channel recovery loop, reopening the channel and resuming operation.
Consumer re-registration
After a connection recovery event, CarrotConsumer.ConsumerChannelUnregisteredAsync() fires. It:
- Stops the current consumer (sends
basic.cancelif the channel is still open, drains in-flight tasks). - Waits one
NetworkRecoveryInterval. - Calls
StartAsync()to create a new consumer channel and re-register with the broker.
In-flight messages during connection loss
What happens to a message being processed when the connection drops depends on the acknowledgement mode:
| Ack mode | Connection drops mid-processing | Outcome |
|---|---|---|
WithSingleAck() |
Ack not sent before connection closes | RabbitMQ requeues the message; it is redelivered to the next available consumer |
WithAckCount(n) |
Batch ack not yet sent | All unacknowledged messages in the batch are requeued and redelivered |
WithAutoAck() |
Message was acked on delivery by RabbitMQ | Message is lost — the broker considers it delivered the moment it was dispatched |
Warning:
WithAutoAck()sacrifices delivery guarantees. PreferWithSingleAck()orWithAckCount(n)for any message that must not be lost.
Graceful Shutdown
Shutdown sequence
When the .NET host stops (e.g. Ctrl+C, SIGTERM, rolling deployment), the following sequence occurs:
- The host cancels
stoppingToken. CarrotService.ExecuteAsync()catches the resultingTaskCanceledException.CarrotConsumerManager.StopConsumingAsync()is called, which callsDisposeAsync()on everyCarrotConsumer.- Each consumer calls
ConsumerChannel.StopConsumingAsync(), which: a. Sendsbasic.cancelto the broker — the broker stops delivering new messages to this consumer. b. CallsRunningTaskRegistry.CompleteAddingAsync()— blocks until every in-flight handler task has completed. - Once all consumers have drained, the connection is closed cleanly.
Drain guarantee
CarrotMQ waits for every in-flight handler to finish before closing channels. No acknowledged message is left mid-processing by the library itself.
Hard deadline — host ShutdownTimeout
CarrotMQ has no configurable drain timeout of its own. The only hard deadline is the .NET host's ShutdownTimeout, which defaults to 30 seconds. If the drain takes longer than this, the host forcefully terminates the process.
Extend the timeout if your handlers need more time to complete:
services.Configure<HostOptions>(o =>
{
o.ShutdownTimeout = TimeSpan.FromMinutes(2);
});
Warning: With
WithAutoAck(), messages that arrive afterbasic.cancelis sent but before the consumer fully stops may be dropped silently. Use manual ack modes if you need a clean shutdown guarantee.