Skip to content

Commit 0c27c17

Browse files
committed
New option to indefinitely requeue a message on an exception criteria. Closes GH-1182
1 parent 82277c0 commit 0c27c17

File tree

4 files changed

+116
-2
lines changed

4 files changed

+116
-2
lines changed

src/Samples/DocumentationSamples/ExceptionHandling.cs

+9-1
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,12 @@ public static async Task with_scripted_error_handling()
5454
// Or instead you could just discard the message and stop
5555
// all processing too!
5656
.Then.Discard().AndPauseProcessing(5.Minutes());
57+
58+
// Obviously use this with caution, but this allows you
59+
// to tell Wolverine to requeue an exception on failures no
60+
// matter how many attempts have been made already
61+
opts.OnException<NotReadyException>()
62+
.RequeueIndefinitely();
5763
}).StartAsync();
5864

5965
#endregion
@@ -88,4 +94,6 @@ public static async Task with_scheduled_retry()
8894
}
8995
}
9096

91-
public class SqlException : Exception;
97+
public class SqlException : Exception;
98+
99+
public class NotReadyException : Exception;

src/Testing/CoreTests/Acceptance/indefinite_scheduled_retries.cs

+21-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
namespace CoreTests.Acceptance;
88

9-
public class indefinite_scheduled_retires
9+
public class indefinite_error_handling
1010
{
1111
[Fact]
1212
public async Task should_indefinitively_retry_command()
@@ -26,6 +26,26 @@ public async Task should_indefinitively_retry_command()
2626
catch (OperationCanceledException) { }
2727
IndefiniteRetriesHandler.CalledCount.ShouldBe(5);
2828
}
29+
30+
[Fact]
31+
public async Task should_indefinitively_requeue_command()
32+
{
33+
IndefiniteRetriesHandler.CalledCount = 0;
34+
using var cts = new CancellationTokenSource(5.Seconds());
35+
using var host = await Host.CreateDefaultBuilder()
36+
.UseWolverine(opts => opts.Policies.OnException<IndefiniteRetryException>().RequeueIndefinitely())
37+
.StartAsync();
38+
39+
var messageBus = host.MessageBus();
40+
await messageBus.SendAsync(new IndefiniteRetriesCommand(cts, SucceedAfterAttempts: 5));
41+
try
42+
{
43+
await Task.Delay(Timeout.Infinite, cts.Token);
44+
}
45+
catch (OperationCanceledException) { }
46+
IndefiniteRetriesHandler.CalledCount.ShouldBe(5);
47+
}
48+
2949
[Fact]
3050
public async Task should_indefinitively_retry_command_when_given_multiple_delays()
3151
{
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
using System.Diagnostics;
2+
using System.Reflection;
3+
using JasperFx.CodeGeneration;
4+
using JasperFx.CodeGeneration.Model;
5+
using Microsoft.Extensions.DependencyInjection;
6+
using Microsoft.Extensions.Hosting;
7+
using Spectre.Console;
8+
using Wolverine.ErrorHandling;
9+
using Wolverine.Runtime.Handlers;
10+
using Xunit;
11+
12+
namespace CoreTests.Bugs;
13+
14+
public class Bug_1182_infinite_loop_codegen
15+
{
16+
[Fact]
17+
public async Task do_not_go_into_infinite_loop()
18+
{
19+
using var host = await Host.CreateDefaultBuilder()
20+
.UseWolverine(opts =>
21+
{
22+
opts.Discovery.DisableConventionalDiscovery().IncludeType(typeof(InfiniteCommandHandlingThing));
23+
}).StartAsync();
24+
25+
var collections = host.Services.GetServices<ICodeFileCollection>().ToArray();
26+
27+
var builder = new DynamicCodeBuilder(host.Services, collections)
28+
{
29+
ServiceVariableSource = host.Services.GetService<IServiceVariableSource>()
30+
};
31+
32+
var ex = Should.Throw<CodeGenerationException>(() => builder.GenerateAllCode());
33+
ex.InnerException.InnerException.ShouldBeOfType<ArgumentOutOfRangeException>();
34+
35+
}
36+
}
37+
38+
public static class InfiniteCommandHandlingThing
39+
{
40+
public static void Configure(HandlerChain chain)
41+
{
42+
chain.OnException<Exception>().Requeue(int.MaxValue);
43+
}
44+
45+
public static void Handle(InfiniteCommand command)
46+
{
47+
}
48+
}
49+
50+
public record InfiniteCommand;

src/Wolverine/ErrorHandling/PolicyExpression.cs

+36
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,10 @@ public IAdditionalActions MoveToErrorQueue()
130130

131131
public IAdditionalActions Requeue(int maxAttempts = 3)
132132
{
133+
if (maxAttempts > 25)
134+
throw new ArgumentOutOfRangeException(nameof(maxAttempts),
135+
"Wolverine allows a maximum of 25 attempts, see the RequeueIndefinitely() option");
136+
133137
for (var i = 0; i < maxAttempts - 1; i++)
134138
{
135139
var slot = _rule.AddSlot(RequeueContinuation.Instance);
@@ -138,6 +142,13 @@ public IAdditionalActions Requeue(int maxAttempts = 3)
138142

139143
return this;
140144
}
145+
146+
public IAdditionalActions RequeueIndefinitely()
147+
{
148+
_rule.InfiniteSource = RequeueContinuation.Instance;
149+
150+
return this;
151+
}
141152

142153
public IAdditionalActions PauseThenRequeue(TimeSpan delay)
143154
{
@@ -159,6 +170,10 @@ public IAdditionalActions ScheduleRetry(params TimeSpan[] delays)
159170
{
160171
throw new InvalidOperationException("You must specify at least one delay time");
161172
}
173+
174+
if (delays.Length > 25)
175+
throw new ArgumentOutOfRangeException(nameof(delays),
176+
"Wolverine allows a maximum of 25 attempts, see the ScheduleRetryIndefinitely() option");
162177

163178
for (var i = 0; i < delays.Length; i++)
164179
{
@@ -200,6 +215,10 @@ public IAdditionalActions RetryTimes(int attempts)
200215
{
201216
throw new ArgumentOutOfRangeException(nameof(attempts));
202217
}
218+
219+
if (attempts > 25)
220+
throw new ArgumentOutOfRangeException(nameof(attempts),
221+
"Wolverine allows a maximum of 25 attempts, maybe see one of the indefinite requeue or reschedule policies");
203222

204223
for (var i = 0; i < attempts; i++)
205224
{
@@ -216,6 +235,11 @@ public IAdditionalActions RetryWithCooldown(params TimeSpan[] delays)
216235
{
217236
throw new InvalidOperationException("You must specify at least one delay time");
218237
}
238+
239+
if (delays.Length > 25)
240+
throw new ArgumentOutOfRangeException(nameof(delays),
241+
"Wolverine allows a maximum of 25 attempts, maybe see one of the indefinite requeue or reschedule policies");
242+
219243

220244
for (var i = 0; i < delays.Length; i++)
221245
{
@@ -280,6 +304,13 @@ public interface IFailureActions
280304
/// <param name="maxAttempts">The maximum number of attempts to process the message. The default is 3</param>
281305
IAdditionalActions Requeue(int maxAttempts = 3);
282306

307+
/// <summary>
308+
/// Requeue the message back to the incoming transport no matter how many times
309+
/// the message has failed. Use with caution obviously!!!!!
310+
/// </summary>
311+
/// <returns></returns>
312+
IAdditionalActions RequeueIndefinitely();
313+
283314
/// <summary>
284315
/// Discard the message without any further attempt to process the message
285316
/// </summary>
@@ -365,6 +396,11 @@ public IAdditionalActions Requeue(int maxAttempts = 3)
365396
return new FailureActions(_match, _parent).Requeue(maxAttempts);
366397
}
367398

399+
public IAdditionalActions RequeueIndefinitely()
400+
{
401+
return new FailureActions(_match, _parent).RequeueIndefinitely();
402+
}
403+
368404
/// <summary>
369405
/// Discard the message without any further attempt to process the message
370406
/// </summary>

0 commit comments

Comments
 (0)