@@ -16,7 +16,6 @@ internal sealed class RabbitMqBus : ITransport, IAsyncDisposable
1616 private readonly RabbitMqBusStartupStatus _startupStatus ;
1717 private readonly ILogger < RabbitMqBus > _logger ;
1818 private readonly ILoggerFactory _loggerFactory ;
19- private readonly MessageTypeCache _typeCache ;
2019 private readonly List < RabbitMqConsumerWorker > _workers = [ ] ;
2120
2221 public RabbitMqBus (
@@ -33,23 +32,27 @@ public RabbitMqBus(
3332 _startupStatus = startupStatus ;
3433 _logger = logger ;
3534 _loggerFactory = loggerFactory ;
36- _typeCache = new MessageTypeCache ( messageConfigurationProvider ) ;
3735 }
3836
3937 /// <remarks>
4038 /// A failed start disposes any partially-created consumer workers and rethrows without faulting the readiness
4139 /// signal, so the hosting consumer service can retry a transient failure (such as a broker that is still coming
42- /// up) while <see cref="RabbitMqBusStartupStatus.Ready"/> stays pending until a start attempt succeeds.
40+ /// up) while <see cref="RabbitMqBusStartupStatus.Ready"/> stays pending until a start attempt succeeds. A fresh
41+ /// type cache is built for every attempt (rather than kept as instance state) so a retry after a partial failure
42+ /// re-registers queues against an empty registry instead of appending to handlers (or request-consumer
43+ /// bookkeeping) left over from the attempt that failed.
4344 /// </remarks>
4445 public async Task StartAsync ( CancellationToken cancellationToken = default )
4546 {
47+ var typeCache = new MessageTypeCache ( _messageConfigurationProvider ) ;
48+
4649 try
4750 {
4851 var queues = _messageConfigurationProvider . QueueDefinitions ;
4952 MessagingLog . BusStarting ( _logger , queues . Count ) ;
5053
51- await SetupTopology ( queues , cancellationToken ) ;
52- await StartConsumersAsync ( queues , cancellationToken ) ;
54+ await SetupTopology ( queues , typeCache , cancellationToken ) ;
55+ await StartConsumersAsync ( queues , typeCache , cancellationToken ) ;
5356
5457 MessagingLog . BusStarted ( _logger ) ;
5558 _startupStatus . MarkStarted ( ) ;
@@ -69,15 +72,15 @@ public Task WaitUntilReadyAsync(CancellationToken cancellationToken = default) =
6972 /// partition lanes in arrival order; parallelism comes from the lanes (bounded by <c>PrefetchCount</c>) rather
7073 /// than concurrent dispatch.
7174 /// </remarks>
72- private async Task StartConsumersAsync ( IReadOnlyCollection < QueueDefinition > queues , CancellationToken cancellationToken )
75+ private async Task StartConsumersAsync ( IReadOnlyCollection < QueueDefinition > queues , MessageTypeCache typeCache , CancellationToken cancellationToken )
7376 {
7477 var workerLogger = _loggerFactory . CreateLogger < RabbitMqConsumerWorker > ( ) ;
7578
7679 foreach ( var queue in queues )
7780 {
78- _typeCache . RegisterQueue ( queue ) ;
81+ typeCache . RegisterQueue ( queue ) ;
7982
80- var partitioned = _typeCache . IsQueuePartitioned ( queue ) ;
83+ var partitioned = typeCache . IsQueuePartitioned ( queue ) ;
8184 var channelCount = partitioned ? 1 : queue . ChannelCount ;
8285 var dispatchConcurrency = partitioned ? ( ushort ) 1 : queue . ConcurrencyLimit ;
8386
@@ -96,7 +99,7 @@ private async Task StartConsumersAsync(IReadOnlyCollection<QueueDefinition> queu
9699 _serviceScopeFactory ,
97100 queue ,
98101 channel ,
99- _typeCache ,
102+ typeCache ,
100103 _messageConfigurationProvider ,
101104 workerLogger ,
102105 i ,
@@ -113,7 +116,7 @@ private async Task StartConsumersAsync(IReadOnlyCollection<QueueDefinition> queu
113116 /// here by convention with the faulted message's URN as the routing key, so a subscriber binds its queue with
114117 /// <c>"#"</c> to observe all faults or with a specific URN to filter by faulted message type.
115118 /// </remarks>
116- private async Task SetupTopology ( IReadOnlyCollection < QueueDefinition > queues , CancellationToken cancellationToken )
119+ private async Task SetupTopology ( IReadOnlyCollection < QueueDefinition > queues , MessageTypeCache typeCache , CancellationToken cancellationToken )
117120 {
118121 using var channel = await _connection . CreateChannelAsync ( cancellationToken : cancellationToken ) ;
119122
@@ -126,7 +129,7 @@ await channel.ExchangeDeclareAsync(
126129
127130 foreach ( var queue in queues )
128131 {
129- await SetupQueueTopology ( queue , channel , cancellationToken ) ;
132+ await SetupQueueTopology ( queue , typeCache , channel , cancellationToken ) ;
130133 MessagingLog . QueueDeclared ( _logger , queue . Name , queue . Registrations . Count ) ;
131134 }
132135 }
@@ -136,7 +139,7 @@ await channel.ExchangeDeclareAsync(
136139 /// active (others stand by for failover) so ordering survives across load-balanced consumers. Partitioned queues
137140 /// opt in automatically; any queue can request it explicitly.
138141 /// </remarks>
139- private async Task SetupQueueTopology ( QueueDefinition queue , IChannel channel , CancellationToken cancellationToken )
142+ private async Task SetupQueueTopology ( QueueDefinition queue , MessageTypeCache typeCache , IChannel channel , CancellationToken cancellationToken )
140143 {
141144 await channel . ExchangeDeclareAsync (
142145 exchange : queue . Name ,
@@ -152,7 +155,7 @@ await channel.ExchangeDeclareAsync(
152155 args . Add ( "x-queue-type" , "quorum" ) ;
153156 }
154157
155- if ( queue . SingleActiveConsumer || _typeCache . IsQueuePartitioned ( queue ) )
158+ if ( queue . SingleActiveConsumer || typeCache . IsQueuePartitioned ( queue ) )
156159 {
157160 args . Add ( "x-single-active-consumer" , true ) ;
158161 }
0 commit comments