|
2 | 2 | { |
3 | 3 | using System; |
4 | 4 | using System.CommandLine; |
5 | | - using System.Globalization; |
6 | | - using System.Text; |
7 | 5 | using System.Threading.Tasks; |
8 | 6 | using global::RabbitMQ.Client; |
9 | 7 |
|
10 | 8 | class DelaysMigrateCommand(BrokerConnection brokerConnection, IRoutingTopology routingTopology, TextWriter output) |
11 | 9 | { |
12 | 10 | const string poisonMessageQueue = "delays-migrate-poison-messages"; |
13 | | - const string timeSentHeader = "NServiceBus.TimeSent"; |
14 | | - const string dateTimeOffsetWireFormat = "yyyy-MM-dd HH:mm:ss:ffffff Z"; |
15 | | - const int indexStartOfDestinationQueue = DelayInfrastructure.MaxNumberOfBitsToUse * 2; |
16 | 11 |
|
17 | 12 | public static Command CreateCommand() |
18 | 13 | { |
@@ -45,132 +40,11 @@ public static Command CreateCommand() |
45 | 40 | async Task Run(CancellationToken cancellationToken) |
46 | 41 | { |
47 | 42 | await using var connection = await brokerConnection.Create(cancellationToken); |
| 43 | + |
48 | 44 | var createChannelOptions = new CreateChannelOptions(publisherConfirmationsEnabled: true, publisherConfirmationTrackingEnabled: true, outstandingPublisherConfirmationsRateLimiter: null); |
49 | 45 | await using var channel = await connection.CreateChannelAsync(createChannelOptions, cancellationToken); |
50 | 46 |
|
51 | | - for (int currentDelayLevel = DelayInfrastructure.MaxLevel; currentDelayLevel >= 0 && !cancellationToken.IsCancellationRequested; currentDelayLevel--) |
52 | | - { |
53 | | - await MigrateQueue(channel, currentDelayLevel, cancellationToken); |
54 | | - } |
| 47 | + await DelayCommandHelpers.TransferMessages(sourceIsV1: true, channel, channel, poisonMessageQueue, routingTopology, output, cancellationToken); |
55 | 48 | } |
56 | | - |
57 | | - async Task MigrateQueue(IChannel channel, int delayLevel, CancellationToken cancellationToken) |
58 | | - { |
59 | | - var currentDelayQueue = $"nsb.delay-level-{delayLevel:00}"; |
60 | | - var messageCount = await channel.MessageCountAsync(currentDelayQueue, cancellationToken); |
61 | | - var declaredDestinationQueues = new HashSet<string>(); |
62 | | - |
63 | | - if (messageCount > 0) |
64 | | - { |
65 | | - output.Write($"Processing {messageCount} messages at delay level {delayLevel:00}. "); |
66 | | - |
67 | | - int skippedMessages = 0; |
68 | | - int processedMessages = 0; |
69 | | - |
70 | | - for (int i = 0; i < messageCount && !cancellationToken.IsCancellationRequested; i++) |
71 | | - { |
72 | | - var message = await channel.BasicGetAsync(currentDelayQueue, false, cancellationToken); |
73 | | - |
74 | | - if (message == null) |
75 | | - { |
76 | | - // Queue is empty |
77 | | - break; |
78 | | - } |
79 | | - |
80 | | - if (MessageIsInvalid(message)) |
81 | | - { |
82 | | - skippedMessages++; |
83 | | - |
84 | | - if (!poisonQueueCreated) |
85 | | - { |
86 | | - await channel.QueueDeclareAsync(poisonMessageQueue, true, false, false, cancellationToken: cancellationToken); |
87 | | - poisonQueueCreated = true; |
88 | | - } |
89 | | - |
90 | | - await channel.BasicPublishAsync(string.Empty, poisonMessageQueue, false, new BasicProperties(message.BasicProperties), message.Body, cancellationToken: cancellationToken); |
91 | | - await channel.BasicAckAsync(message.DeliveryTag, false, cancellationToken); |
92 | | - |
93 | | - continue; |
94 | | - } |
95 | | - |
96 | | - var messageHeaders = message.BasicProperties.Headers; |
97 | | - |
98 | | - var delayInSeconds = 0; |
99 | | - |
100 | | - if (messageHeaders is not null) |
101 | | - { |
102 | | - var headerValue = messageHeaders[DelayInfrastructure.DelayHeader]; |
103 | | - |
104 | | - if (headerValue is not null) |
105 | | - { |
106 | | - delayInSeconds = (int)headerValue; |
107 | | - } |
108 | | - } |
109 | | - |
110 | | - var timeSent = GetTimeSent(message); |
111 | | - |
112 | | - var (destinationQueue, newRoutingKey, newDelayLevel) = GetNewRoutingKey(delayInSeconds, timeSent, message.RoutingKey, DateTimeOffset.UtcNow); |
113 | | - |
114 | | - // Make sure the destination queue is bound to the delivery exchange to ensure delivery |
115 | | - if (!declaredDestinationQueues.Contains(destinationQueue)) |
116 | | - { |
117 | | - await routingTopology.BindToDelayInfrastructure(channel, destinationQueue, DelayInfrastructure.DeliveryExchange, DelayInfrastructure.BindingKey(destinationQueue), cancellationToken); |
118 | | - declaredDestinationQueues.Add(destinationQueue); |
119 | | - } |
120 | | - |
121 | | - var publishExchange = DelayInfrastructure.LevelName(newDelayLevel); |
122 | | - |
123 | | - if (messageHeaders != null) |
124 | | - { |
125 | | - //These headers need to be removed so that they won't be copied to an outgoing message if this message gets forwarded |
126 | | - messageHeaders.Remove(DelayInfrastructure.XDeathHeader); |
127 | | - messageHeaders.Remove(DelayInfrastructure.XFirstDeathExchangeHeader); |
128 | | - messageHeaders.Remove(DelayInfrastructure.XFirstDeathQueueHeader); |
129 | | - messageHeaders.Remove(DelayInfrastructure.XFirstDeathReasonHeader); |
130 | | - } |
131 | | - |
132 | | - await channel.BasicPublishAsync(publishExchange, newRoutingKey, false, new BasicProperties(message.BasicProperties), message.Body, cancellationToken: cancellationToken); |
133 | | - await channel.BasicAckAsync(message.DeliveryTag, false, cancellationToken); |
134 | | - processedMessages++; |
135 | | - } |
136 | | - |
137 | | - output.WriteLine($"{processedMessages} successful, {skippedMessages} skipped."); |
138 | | - } |
139 | | - else |
140 | | - { |
141 | | - output.WriteLine($"No messages to process at delay level {delayLevel:00}."); |
142 | | - } |
143 | | - } |
144 | | - |
145 | | - public static (string DestinationQueue, string NewRoutingKey, int NewDelayLevel) GetNewRoutingKey(int delayInSeconds, DateTimeOffset timeSent, string currentRoutingKey, DateTimeOffset utcNow) |
146 | | - { |
147 | | - var originalDeliveryDate = timeSent.AddSeconds(delayInSeconds); |
148 | | - var newDelayInSeconds = Convert.ToInt32(originalDeliveryDate.Subtract(utcNow).TotalSeconds); |
149 | | - var destinationQueue = currentRoutingKey[indexStartOfDestinationQueue..]; |
150 | | - var newRoutingKey = DelayInfrastructure.CalculateRoutingKey(newDelayInSeconds, destinationQueue, out int newDelayLevel); |
151 | | - |
152 | | - return (destinationQueue, newRoutingKey, newDelayLevel); |
153 | | - } |
154 | | - |
155 | | - static DateTimeOffset GetTimeSent(BasicGetResult message) |
156 | | - { |
157 | | - var timeSentHeaderValue = message.BasicProperties.Headers?[timeSentHeader]; |
158 | | - var timeSentHeaderBytes = Array.Empty<byte>(); |
159 | | - |
160 | | - if (timeSentHeaderValue is not null) |
161 | | - { |
162 | | - timeSentHeaderBytes = (byte[])timeSentHeaderValue; |
163 | | - } |
164 | | - |
165 | | - var timeSentString = Encoding.UTF8.GetString(timeSentHeaderBytes); |
166 | | - return DateTimeOffset.ParseExact(timeSentString, dateTimeOffsetWireFormat, CultureInfo.InvariantCulture); |
167 | | - } |
168 | | - |
169 | | - static bool MessageIsInvalid(BasicGetResult? message) => |
170 | | - message?.BasicProperties?.Headers == null |
171 | | - || !message.BasicProperties.Headers.ContainsKey(DelayInfrastructure.DelayHeader) |
172 | | - || !message.BasicProperties.Headers.ContainsKey(timeSentHeader); |
173 | | - |
174 | | - bool poisonQueueCreated = false; |
175 | 49 | } |
176 | 50 | } |
0 commit comments