@@ -37,22 +37,21 @@ public RabbitMqBus(
3737 /// <remarks>
3838 /// A failed start disposes any partially-created consumer workers and rethrows without faulting the readiness
3939 /// signal, so the hosting consumer service can retry a transient failure (such as a broker that is still coming
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.
40+ /// up) while <see cref="RabbitMqBusStartupStatus.Ready"/> stays pending until a start attempt succeeds. Fresh
41+ /// per-queue type caches are built for every attempt (rather than kept as instance state) so a retry after a
42+ /// partial failure re-registers each queue against an empty registry instead of appending to handlers (or
43+ /// request-consumer bookkeeping) left over from the attempt that failed.
4444 /// </remarks>
4545 public async Task StartAsync ( CancellationToken cancellationToken = default )
4646 {
47- var typeCache = new MessageTypeCache ( _messageConfigurationProvider ) ;
48-
4947 try
5048 {
5149 var queues = _messageConfigurationProvider . QueueDefinitions ;
5250 MessagingLog . BusStarting ( _logger , queues . Count ) ;
5351
54- await SetupTopology ( queues , typeCache , cancellationToken ) ;
55- await StartConsumersAsync ( queues , typeCache , cancellationToken ) ;
52+ var typeCaches = BuildTypeCaches ( queues ) ;
53+ await SetupTopology ( queues , typeCaches , cancellationToken ) ;
54+ await StartConsumersAsync ( queues , typeCaches , cancellationToken ) ;
5655
5756 MessagingLog . BusStarted ( _logger ) ;
5857 _startupStatus . MarkStarted ( ) ;
@@ -64,6 +63,25 @@ public async Task StartAsync(CancellationToken cancellationToken = default)
6463 }
6564 }
6665
66+ /// <summary>
67+ /// Builds one <see cref="MessageTypeCache"/> per queue, each registering only that queue. Several queues may
68+ /// consume the same message type, and the broker delivers a distinct copy to each of them; scoping the plan
69+ /// cache to its queue keeps a delivery from also dispatching the consumers every other queue registered for
70+ /// that type.
71+ /// </summary>
72+ internal Dictionary < string , MessageTypeCache > BuildTypeCaches ( IReadOnlyCollection < QueueDefinition > queues )
73+ {
74+ var typeCaches = new Dictionary < string , MessageTypeCache > ( StringComparer . OrdinalIgnoreCase ) ;
75+ foreach ( var queue in queues )
76+ {
77+ var typeCache = new MessageTypeCache ( _messageConfigurationProvider ) ;
78+ typeCache . RegisterQueue ( queue ) ;
79+ typeCaches [ queue . Name ] = typeCache ;
80+ }
81+
82+ return typeCaches ;
83+ }
84+
6785 public Task WaitUntilReadyAsync ( CancellationToken cancellationToken = default ) =>
6886 _startupStatus . Ready . WaitAsync ( cancellationToken ) ;
6987
@@ -72,14 +90,13 @@ public Task WaitUntilReadyAsync(CancellationToken cancellationToken = default) =
7290 /// partition lanes in arrival order; parallelism comes from the lanes (bounded by <c>PrefetchCount</c>) rather
7391 /// than concurrent dispatch.
7492 /// </remarks>
75- private async Task StartConsumersAsync ( IReadOnlyCollection < QueueDefinition > queues , MessageTypeCache typeCache , CancellationToken cancellationToken )
93+ private async Task StartConsumersAsync ( IReadOnlyCollection < QueueDefinition > queues , Dictionary < string , MessageTypeCache > typeCaches , CancellationToken cancellationToken )
7694 {
7795 var workerLogger = _loggerFactory . CreateLogger < RabbitMqConsumerWorker > ( ) ;
7896
7997 foreach ( var queue in queues )
8098 {
81- typeCache . RegisterQueue ( queue ) ;
82-
99+ var typeCache = typeCaches [ queue . Name ] ;
83100 var partitioned = typeCache . IsQueuePartitioned ( queue ) ;
84101 var channelCount = partitioned ? 1 : queue . ChannelCount ;
85102 var dispatchConcurrency = partitioned ? ( ushort ) 1 : queue . ConcurrencyLimit ;
@@ -116,7 +133,7 @@ private async Task StartConsumersAsync(IReadOnlyCollection<QueueDefinition> queu
116133 /// here by convention with the faulted message's URN as the routing key, so a subscriber binds its queue with
117134 /// <c>"#"</c> to observe all faults or with a specific URN to filter by faulted message type.
118135 /// </remarks>
119- private async Task SetupTopology ( IReadOnlyCollection < QueueDefinition > queues , MessageTypeCache typeCache , CancellationToken cancellationToken )
136+ private async Task SetupTopology ( IReadOnlyCollection < QueueDefinition > queues , Dictionary < string , MessageTypeCache > typeCaches , CancellationToken cancellationToken )
120137 {
121138 using var channel = await _connection . CreateChannelAsync ( cancellationToken : cancellationToken ) ;
122139
@@ -129,7 +146,7 @@ await channel.ExchangeDeclareAsync(
129146
130147 foreach ( var queue in queues )
131148 {
132- await SetupQueueTopology ( queue , typeCache , channel , cancellationToken ) ;
149+ await SetupQueueTopology ( queue , typeCaches [ queue . Name ] , channel , cancellationToken ) ;
133150 MessagingLog . QueueDeclared ( _logger , queue . Name , queue . Registrations . Count ) ;
134151 }
135152 }
0 commit comments