Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
using JasperFx;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Shouldly;
using Wolverine;
using Wolverine.Tracking;
using Xunit;

namespace CoreTests.Acceptance;

public class on_exception_same_type_in_two_middlewares
{
[Fact]
public async Task both_handlers_run_in_registration_order()
{
var recorder = new SameTypeRecorder();
using var host = await Host.CreateDefaultBuilder()
.UseWolverine(opts =>
{
opts.Services.AddSingleton(recorder);
opts.Discovery.DisableConventionalDiscovery().IncludeType(typeof(SameTypeTriggerHandler));
opts.Policies.AddMiddleware(typeof(FirstSameTypeMiddleware));
opts.Policies.AddMiddleware(typeof(SecondSameTypeMiddleware));
}).StartAsync(TestContext.Current.CancellationToken);

await host.InvokeMessageAndWaitAsync(new SameTypeTrigger());

recorder.Calls.ShouldBe(["first", "second"]);
}

[Fact]
public async Task both_handlers_can_return_a_value()
{
var recorder = new SameTypeRecorder();
using var host = await Host.CreateDefaultBuilder()
.UseWolverine(opts =>
{
opts.Services.AddSingleton(recorder);
opts.Discovery.DisableConventionalDiscovery()
.IncludeType(typeof(SameTypeTriggerHandler))
.IncludeType(typeof(SameTypeCascadeHandler));
opts.Policies.AddMiddleware(typeof(FirstCascadingMiddleware));
opts.Policies.AddMiddleware(typeof(SecondCascadingMiddleware));
}).StartAsync(TestContext.Current.CancellationToken);

await host.TrackActivity()
.DoNotAssertOnExceptionsDetected()
.InvokeMessageAndWaitAsync(new SameTypeTrigger());

recorder.Calls.ShouldBe(["first", "second"], ignoreOrder: true);
}

[Fact]
public async Task both_handlers_can_return_a_continuation()
{
var recorder = new SameTypeRecorder();
using var host = await Host.CreateDefaultBuilder()
.UseWolverine(opts =>
{
opts.Services.AddSingleton(recorder);
opts.Discovery.DisableConventionalDiscovery().IncludeType(typeof(SameTypeTriggerHandler));
opts.Policies.AddMiddleware(typeof(FirstContinuationMiddleware));
opts.Policies.AddMiddleware(typeof(SecondContinuationMiddleware));
}).StartAsync(TestContext.Current.CancellationToken);

await host.InvokeMessageAndWaitAsync(new SameTypeTrigger());

recorder.Calls.ShouldBe(["first", "second"]);
}
}

public static class FirstContinuationMiddleware
{
public static HandlerContinuation OnException(ConcurrencyException ex, SameTypeRecorder recorder)
{
recorder.Calls.Add("first");
return HandlerContinuation.Continue;
}
}

public static class SecondContinuationMiddleware
{
public static HandlerContinuation OnException(ConcurrencyException ex, SameTypeRecorder recorder)
{
recorder.Calls.Add("second");
return HandlerContinuation.Continue;
}
}

public record SameTypeCascade(string From);

public static class SameTypeCascadeHandler
{
public static void Handle(SameTypeCascade message, SameTypeRecorder recorder) => recorder.Calls.Add(message.From);
}

public static class FirstCascadingMiddleware
{
public static SameTypeCascade OnException(ConcurrencyException ex) => new("first");
}

public static class SecondCascadingMiddleware
{
public static SameTypeCascade OnException(ConcurrencyException ex) => new("second");
}

public class SameTypeRecorder
{
public ActionLog Calls { get; } = new();
}

public record SameTypeTrigger;

public static class SameTypeTriggerHandler
{
public static void Handle(SameTypeTrigger message) =>
throw new ConcurrencyException("conflict", typeof(SameTypeTrigger), Guid.NewGuid());
}

public static class FirstSameTypeMiddleware
{
public static void OnException(ConcurrencyException ex, SameTypeRecorder recorder) => recorder.Calls.Add("first");
}

public static class SecondSameTypeMiddleware
{
public static void OnException(ConcurrencyException ex, SameTypeRecorder recorder) => recorder.Calls.Add("second");
}
24 changes: 23 additions & 1 deletion src/Wolverine/Middleware/TryCatchFinallyFrame.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,28 @@ public TryCatchFinallyFrame() : base(true)

public void AddCatchBlock(Type exceptionType, Frame[] frames)
{
// C# rejects a second catch clause for the same type (CS0160).
var existing = _catchBlocks.FirstOrDefault(x => x.ExceptionType == exceptionType);
if (existing != null)
{
// The merged handlers share one scope, so a variable can't reuse an earlier handler's name (CS0128).
var used = existing.Frames.SelectMany(x => x.Creates).Select(x => x.Usage).ToHashSet();
foreach (var variable in frames.SelectMany(x => x.Creates).Where(x => !x.VariableType.IsValueTuple()))
{
var name = variable.Usage;
for (var i = 1; used.Contains(name); i++)
{
name = $"{variable.Usage}{i}";
}

variable.OverrideName(name);
used.Add(name);
}

existing.Frames = [..existing.Frames, ..frames];
return;
}

_catchBlocks.Add(new CatchBlock(exceptionType, frames));
SortCatchBlocks();
}
Expand Down Expand Up @@ -212,7 +234,7 @@ public CatchBlock(Type exceptionType, Frame[] frames)
}

public Type ExceptionType { get; }
public Frame[] Frames { get; }
public Frame[] Frames { get; internal set; }
public Variable ExceptionVariable { get; }

internal static string DefaultExceptionVariableName(Type exceptionType)
Expand Down