diff --git a/src/Testing/CoreTests/Bugs/Bug_2023_invoke_with_discard_error_handling.cs b/src/Testing/CoreTests/Bugs/Bug_2023_invoke_with_discard_error_handling.cs new file mode 100644 index 000000000..7bb48502e --- /dev/null +++ b/src/Testing/CoreTests/Bugs/Bug_2023_invoke_with_discard_error_handling.cs @@ -0,0 +1,55 @@ +using Microsoft.Extensions.Hosting; +using Wolverine.ErrorHandling; +using Xunit; + +namespace CoreTests.Bugs; + +public class Bug_2023_invoke_with_discard_error_handling +{ + [Fact] + public async Task should_throw_the_exception_from_invoke() + { + using var host = await Host.CreateDefaultBuilder() + .UseWolverine(opts => + { + // By default, this should NOT apply to inline executions (e.g. InvokeAsync), + // and an exception inside an inline execution should simply be surfaced. + // However, as of 3.13.0, this configuration causes the error not to be thrown. + opts.OnAnyException() + .Discard() + .And((runtime, context, ex) => { + // Do some application-specific error handling here... + return new ValueTask(); + }); + }).StartAsync(); + + var bus = host.MessageBus(); + + await Should.ThrowAsync(async () => + { + await bus.InvokeAsync(new Request()); + }); + + + } +} + +public class RequestHandler { +/* + This is the exception that should appear to the user, but it does not. + This should not be caught by the Wolverine error handling because according + to the documentation, + + `When using IMessageBus.InvokeAsync() to execute a message inline, + only the `Retry` and `Retry With Cooldown` error policies are applied + to the execution automatically.` + + So, the `Discard/And` error policies should NOT be applied automatically, + and the error should be thrown (which was the case in 3.9.1, and got broken + by a commit in 3.13.0). +*/ + public string Handle(Request request) + => throw new Exception(@"User-facing error message that should appear to the user."); +} + +public class Request { } \ No newline at end of file diff --git a/src/Testing/CoreTests/ErrorHandling/LambdaContinuationTests.cs b/src/Testing/CoreTests/ErrorHandling/LambdaContinuationTests.cs index 293abdf24..78cc04356 100644 --- a/src/Testing/CoreTests/ErrorHandling/LambdaContinuationTests.cs +++ b/src/Testing/CoreTests/ErrorHandling/LambdaContinuationTests.cs @@ -21,10 +21,13 @@ public async Task execute_as_inline_with_no_invoke_usage() return new ValueTask(); }, new Exception()); - var result = await continuation.ExecuteInlineAsync(Substitute.For(), - new MockWolverineRuntime(), DateTimeOffset.UtcNow, null, CancellationToken.None); - - result.ShouldBe(InvokeResult.Stop); + await Should.ThrowAsync(async () => + { + var result = await continuation.ExecuteInlineAsync(Substitute.For(), + new MockWolverineRuntime(), DateTimeOffset.UtcNow, null, CancellationToken.None); + + }); + wasCalled.ShouldBeFalse(); } diff --git a/src/Wolverine/ErrorHandling/FailureRuleCollection.cs b/src/Wolverine/ErrorHandling/FailureRuleCollection.cs index 2bbe9b84a..4ae0f00b7 100644 --- a/src/Wolverine/ErrorHandling/FailureRuleCollection.cs +++ b/src/Wolverine/ErrorHandling/FailureRuleCollection.cs @@ -71,6 +71,16 @@ internal IContinuation DetermineExecutionContinuation(Exception e, Envelope enve continue; } + if (continuation is CompositeContinuation composite) + { + foreach (var inner in composite.Inner) + { + if (inner is IInlineContinuation inline) return inline; + } + + continue; + } + if (continuation is IInlineContinuation retry) { return retry; diff --git a/src/Wolverine/ErrorHandling/PolicyExpression.cs b/src/Wolverine/ErrorHandling/PolicyExpression.cs index 7b2ad9b9d..f43fbac86 100644 --- a/src/Wolverine/ErrorHandling/PolicyExpression.cs +++ b/src/Wolverine/ErrorHandling/PolicyExpression.cs @@ -1,4 +1,5 @@ using System.Diagnostics; +using System.Runtime.ExceptionServices; using Wolverine.ErrorHandling.Matches; using Wolverine.Runtime; using Wolverine.Runtime.Handlers; @@ -371,10 +372,14 @@ public ValueTask ExecuteAsync(IEnvelopeLifecycle lifecycle, IWolverineRuntime ru public async ValueTask ExecuteInlineAsync(IEnvelopeLifecycle lifecycle, IWolverineRuntime runtime, DateTimeOffset now, Activity? activity, CancellationToken cancellation) { - if (InvokeUsage == null) return InvokeResult.Stop; + if (InvokeUsage == null) + { + ExceptionDispatchInfo.Throw(_exception); + return InvokeResult.Stop; + } await _action(runtime, lifecycle, _exception); - + return InvokeUsage.Value; } }