Skip to content

Commit f95c85a

Browse files
committed
Fix for inline execution behavior of custom error handling actions. Closes GH-2023
1 parent cd33c63 commit f95c85a

4 files changed

Lines changed: 79 additions & 6 deletions

File tree

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
using Microsoft.Extensions.Hosting;
2+
using Wolverine.ErrorHandling;
3+
using Xunit;
4+
5+
namespace CoreTests.Bugs;
6+
7+
public class Bug_2023_invoke_with_discard_error_handling
8+
{
9+
[Fact]
10+
public async Task should_throw_the_exception_from_invoke()
11+
{
12+
using var host = await Host.CreateDefaultBuilder()
13+
.UseWolverine(opts =>
14+
{
15+
// By default, this should NOT apply to inline executions (e.g. InvokeAsync),
16+
// and an exception inside an inline execution should simply be surfaced.
17+
// However, as of 3.13.0, this configuration causes the error not to be thrown.
18+
opts.OnAnyException()
19+
.Discard()
20+
.And((runtime, context, ex) => {
21+
// Do some application-specific error handling here...
22+
return new ValueTask();
23+
});
24+
}).StartAsync();
25+
26+
var bus = host.MessageBus();
27+
28+
await Should.ThrowAsync<Exception>(async () =>
29+
{
30+
await bus.InvokeAsync(new Request());
31+
});
32+
33+
34+
}
35+
}
36+
37+
public class RequestHandler {
38+
/*
39+
This is the exception that should appear to the user, but it does not.
40+
This should not be caught by the Wolverine error handling because according
41+
to the documentation,
42+
43+
`When using IMessageBus.InvokeAsync() to execute a message inline,
44+
only the `Retry` and `Retry With Cooldown` error policies are applied
45+
to the execution automatically.`
46+
47+
So, the `Discard/And` error policies should NOT be applied automatically,
48+
and the error should be thrown (which was the case in 3.9.1, and got broken
49+
by a commit in 3.13.0).
50+
*/
51+
public string Handle(Request request)
52+
=> throw new Exception(@"User-facing error message that should appear to the user.");
53+
}
54+
55+
public class Request { }

src/Testing/CoreTests/ErrorHandling/LambdaContinuationTests.cs

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,13 @@ public async Task execute_as_inline_with_no_invoke_usage()
2121
return new ValueTask();
2222
}, new Exception());
2323

24-
var result = await continuation.ExecuteInlineAsync(Substitute.For<IEnvelopeLifecycle>(),
25-
new MockWolverineRuntime(), DateTimeOffset.UtcNow, null, CancellationToken.None);
26-
27-
result.ShouldBe(InvokeResult.Stop);
24+
await Should.ThrowAsync<Exception>(async () =>
25+
{
26+
var result = await continuation.ExecuteInlineAsync(Substitute.For<IEnvelopeLifecycle>(),
27+
new MockWolverineRuntime(), DateTimeOffset.UtcNow, null, CancellationToken.None);
28+
29+
});
30+
2831
wasCalled.ShouldBeFalse();
2932
}
3033

src/Wolverine/ErrorHandling/FailureRuleCollection.cs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,16 @@ internal IContinuation DetermineExecutionContinuation(Exception e, Envelope enve
7171
continue;
7272
}
7373

74+
if (continuation is CompositeContinuation composite)
75+
{
76+
foreach (var inner in composite.Inner)
77+
{
78+
if (inner is IInlineContinuation inline) return inline;
79+
}
80+
81+
continue;
82+
}
83+
7484
if (continuation is IInlineContinuation retry)
7585
{
7686
return retry;

src/Wolverine/ErrorHandling/PolicyExpression.cs

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using System.Diagnostics;
2+
using System.Runtime.ExceptionServices;
23
using Wolverine.ErrorHandling.Matches;
34
using Wolverine.Runtime;
45
using Wolverine.Runtime.Handlers;
@@ -371,10 +372,14 @@ public ValueTask ExecuteAsync(IEnvelopeLifecycle lifecycle, IWolverineRuntime ru
371372
public async ValueTask<InvokeResult> ExecuteInlineAsync(IEnvelopeLifecycle lifecycle, IWolverineRuntime runtime, DateTimeOffset now,
372373
Activity? activity, CancellationToken cancellation)
373374
{
374-
if (InvokeUsage == null) return InvokeResult.Stop;
375+
if (InvokeUsage == null)
376+
{
377+
ExceptionDispatchInfo.Throw(_exception);
378+
return InvokeResult.Stop;
379+
}
375380

376381
await _action(runtime, lifecycle, _exception);
377-
382+
378383
return InvokeUsage.Value;
379384
}
380385
}

0 commit comments

Comments
 (0)