Skip to content

Commit b989fe3

Browse files
committed
Various hot path improvements and benchmark updates
1 parent 862646f commit b989fe3

18 files changed

Lines changed: 471 additions & 174 deletions

benchmarks/Foundatio.Mediator.Benchmarks/CoreBenchmarks.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,8 @@ public class CoreBenchmarks
3232
private readonly FoundatioQueryHandler _directQueryHandler = new();
3333
private readonly FoundatioEventHandler _directEventHandler = new();
3434
private readonly FoundatioCreateOrderHandler _directCreateOrderHandler = new();
35-
private readonly FoundatioOrderCreatedHandler1 _directOrderCreatedHandler1 = new();
36-
private readonly FoundatioOrderCreatedHandler2 _directOrderCreatedHandler2 = new();
35+
private readonly FoundatioFirstOrderCreatedHandler _directOrderCreatedHandler1 = new();
36+
private readonly FoundatioSecondOrderCreatedHandler _directOrderCreatedHandler2 = new();
3737
private FoundatioFullQueryHandler _directFullQueryHandler = null!;
3838

3939
private readonly PingCommand _pingCommand = new("test-123");
@@ -323,7 +323,7 @@ public async Task<Order> MediatorNet_FullQuery()
323323
[Benchmark]
324324
public async Task<Order> Direct_CascadingMessages()
325325
{
326-
var (order, evt) = await _directCreateOrderHandler.HandleAsync(_createOrder);
326+
var (order, evt) = _directCreateOrderHandler.HandleAsync(_createOrder);
327327
await _directOrderCreatedHandler1.HandleAsync(evt);
328328
await _directOrderCreatedHandler2.HandleAsync(evt);
329329
return order;

benchmarks/Foundatio.Mediator.Benchmarks/Foundatio.Mediator.Benchmarks.csproj

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,14 @@
1717
<MediatorHandlerLifetime>None</MediatorHandlerLifetime>
1818
<MediatorDisableInterceptors>false</MediatorDisableInterceptors>
1919
<MediatorDisableOpenTelemetry>false</MediatorDisableOpenTelemetry>
20+
<MediatorDisableConventionalDiscovery>true</MediatorDisableConventionalDiscovery>
2021
</PropertyGroup>
2122

2223
<ItemGroup>
2324
<CompilerVisibleProperty Include="MediatorDisableInterceptors" />
2425
<CompilerVisibleProperty Include="MediatorHandlerLifetime" />
2526
<CompilerVisibleProperty Include="MediatorDisableOpenTelemetry" />
27+
<CompilerVisibleProperty Include="MediatorDisableConventionalDiscovery" />
2628
<!-- Exclude the output of source generators from the compilation -->
2729
<Compile Remove="$(CompilerGeneratedFilesOutputPath)/**/*.cs" />
2830
<Content Include="$(CompilerGeneratedFilesOutputPath)/**/*.cs" />

benchmarks/Foundatio.Mediator.Benchmarks/FoundatioBenchmarks.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -44,15 +44,15 @@ public async Task Command()
4444
}
4545

4646
[Benchmark]
47-
public async Task<Order> Query()
47+
public ValueTask<Order> Query()
4848
{
49-
return await _foundatioMediator.InvokeAsync<Order>(_getOrder);
49+
return _foundatioMediator.InvokeAsync<Order>(_getOrder);
5050
}
5151

5252
[Benchmark]
53-
public async Task Publish()
53+
public ValueTask Publish()
5454
{
55-
await _foundatioMediator.PublishAsync(_userRegisteredEvent);
55+
return _foundatioMediator.PublishAsync(_userRegisteredEvent);
5656
}
5757

5858
[Benchmark]

benchmarks/Foundatio.Mediator.Benchmarks/Handlers/Foundatio/FoundatioHandlers.cs

Lines changed: 30 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -4,45 +4,49 @@
44
namespace Foundatio.Mediator.Benchmarks.Handlers.Foundatio;
55

66
// Scenario 1: Command handler (InvokeAsync without response)
7+
[Handler]
78
public class FoundatioCommandHandler
89
{
9-
public async Task HandleAsync(PingCommand command, CancellationToken cancellationToken = default)
10+
public Task HandleAsync(PingCommand command, CancellationToken cancellationToken = default)
1011
{
11-
// Simulate minimal work
12-
await Task.CompletedTask;
12+
// Simulate minimal work - no async state machine
13+
return Task.CompletedTask;
1314
}
1415
}
1516

1617
// Scenario 2: Query handler (InvokeAsync<T>)
18+
[Handler]
1719
public class FoundatioQueryHandler
1820
{
19-
public async Task<Order> HandleAsync(GetOrder query, CancellationToken cancellationToken = default)
21+
public ValueTask<Order> HandleAsync(GetOrder query, CancellationToken cancellationToken = default)
2022
{
21-
await Task.CompletedTask;
22-
return new Order(query.Id, 99.99m, DateTime.UtcNow);
23+
return new ValueTask<Order>(new Order(query.Id, 99.99m, DateTime.UtcNow));
2324
}
2425
}
2526

2627
// Scenario 3: Event handlers (PublishAsync with multiple handlers)
28+
[Handler]
2729
public class FoundatioEventHandler
2830
{
29-
public async Task HandleAsync(UserRegisteredEvent notification, CancellationToken cancellationToken = default)
31+
public Task HandleAsync(UserRegisteredEvent notification, CancellationToken cancellationToken = default)
3032
{
31-
// Simulate minimal event handling work
32-
await Task.CompletedTask;
33+
// Simulate minimal event handling work - returns completed task with no allocation
34+
return Task.CompletedTask;
3335
}
3436
}
3537

36-
public class FoundatioEventHandler2
38+
[Handler]
39+
public class FoundatioSecondEventHandler
3740
{
38-
public async Task HandleAsync(UserRegisteredEvent notification, CancellationToken cancellationToken = default)
41+
public Task HandleAsync(UserRegisteredEvent notification, CancellationToken cancellationToken = default)
3942
{
4043
// Second handler listening for the same event
41-
await Task.CompletedTask;
44+
return Task.CompletedTask;
4245
}
4346
}
4447

4548
// Scenario 4: Query handler with dependency injection
49+
[Handler]
4650
public class FoundatioFullQueryHandler
4751
{
4852
private readonly IOrderService _orderService;
@@ -59,36 +63,40 @@ public async Task<Order> HandleAsync(GetFullQuery query, CancellationToken cance
5963
}
6064

6165
// Scenario 5: Cascading messages - returns tuple with result + events that auto-publish
66+
[Handler]
6267
public class FoundatioCreateOrderHandler
6368
{
64-
public async Task<(Order order, OrderCreatedEvent evt)> HandleAsync(CreateOrder command, CancellationToken cancellationToken = default)
69+
public (Order order, OrderCreatedEvent evt) HandleAsync(CreateOrder command, CancellationToken cancellationToken = default)
6570
{
66-
await Task.CompletedTask;
71+
// No async state machine needed
6772
var order = new Order(1, command.Amount, DateTime.UtcNow);
6873
return (order, new OrderCreatedEvent(order.Id, command.CustomerId));
6974
}
7075
}
7176

7277
// Handlers for the cascaded OrderCreatedEvent
73-
public class FoundatioOrderCreatedHandler1
78+
[Handler]
79+
public class FoundatioFirstOrderCreatedHandler
7480
{
75-
public async Task HandleAsync(OrderCreatedEvent notification, CancellationToken cancellationToken = default)
81+
public Task HandleAsync(OrderCreatedEvent notification, CancellationToken cancellationToken = default)
7682
{
77-
// First handler for order created event
78-
await Task.CompletedTask;
83+
// First handler for order created event - no async state machine
84+
return Task.CompletedTask;
7985
}
8086
}
8187

82-
public class FoundatioOrderCreatedHandler2
88+
[Handler]
89+
public class FoundatioSecondOrderCreatedHandler
8390
{
84-
public async Task HandleAsync(OrderCreatedEvent notification, CancellationToken cancellationToken = default)
91+
public Task HandleAsync(OrderCreatedEvent notification, CancellationToken cancellationToken = default)
8592
{
86-
// Second handler for order created event
87-
await Task.CompletedTask;
93+
// Second handler for order created event - no async state machine
94+
return Task.CompletedTask;
8895
}
8996
}
9097

9198
// Scenario 6: Short-circuit handler (never actually called due to ShortCircuitMiddleware)
99+
[Handler]
92100
public class FoundatioShortCircuitHandler
93101
{
94102
public Task<Order> HandleAsync(GetCachedOrder query, CancellationToken cancellationToken = default)

benchmarks/Foundatio.Mediator.Benchmarks/Handlers/MediatR/MediatRHandlers.cs

Lines changed: 18 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -5,50 +5,45 @@
55
namespace Foundatio.Mediator.Benchmarks.Handlers.MediatR;
66

77
// Scenario 1: Command handler (InvokeAsync without response)
8-
[FoundatioIgnore]
98
public class MediatRCommandHandler : IRequestHandler<PingCommand>
109
{
11-
public async Task Handle(PingCommand request, CancellationToken cancellationToken)
10+
public Task Handle(PingCommand request, CancellationToken cancellationToken)
1211
{
13-
// Simulate minimal work
14-
await Task.CompletedTask;
12+
// Simulate minimal work - no async state machine
13+
return Task.CompletedTask;
1514
}
1615
}
1716

1817
// Scenario 2: Query handler (InvokeAsync<T>) - No DI for baseline comparison
19-
[FoundatioIgnore]
2018
public class MediatRQueryHandler : IRequestHandler<GetOrder, Order>
2119
{
22-
public async Task<Order> Handle(GetOrder request, CancellationToken cancellationToken)
20+
public Task<Order> Handle(GetOrder request, CancellationToken cancellationToken)
2321
{
24-
await Task.CompletedTask;
25-
return new Order(request.Id, 99.99m, DateTime.UtcNow);
22+
// No async state machine - return Task.FromResult
23+
return Task.FromResult(new Order(request.Id, 99.99m, DateTime.UtcNow));
2624
}
2725
}
2826

2927
// Scenario 3: Event handlers (PublishAsync with multiple handlers)
30-
[FoundatioIgnore]
3128
public class MediatREventHandler : INotificationHandler<UserRegisteredEvent>
3229
{
33-
public async Task Handle(UserRegisteredEvent notification, CancellationToken cancellationToken)
30+
public Task Handle(UserRegisteredEvent notification, CancellationToken cancellationToken)
3431
{
35-
// Simulate minimal event handling work
36-
await Task.CompletedTask;
32+
// Simulate minimal event handling work - no async state machine
33+
return Task.CompletedTask;
3734
}
3835
}
3936

40-
[FoundatioIgnore]
4137
public class MediatREventHandler2 : INotificationHandler<UserRegisteredEvent>
4238
{
43-
public async Task Handle(UserRegisteredEvent notification, CancellationToken cancellationToken)
39+
public Task Handle(UserRegisteredEvent notification, CancellationToken cancellationToken)
4440
{
45-
// Second handler listening for the same event
46-
await Task.CompletedTask;
41+
// Second handler listening for the same event - no async state machine
42+
return Task.CompletedTask;
4743
}
4844
}
4945

5046
// Scenario 4: Query handler with dependency injection
51-
[FoundatioIgnore]
5247
public class MediatRFullQueryHandler : IRequestHandler<GetFullQuery, Order>
5348
{
5449
private readonly IOrderService _orderService;
@@ -65,7 +60,6 @@ public async Task<Order> Handle(GetFullQuery request, CancellationToken cancella
6560
}
6661

6762
// Scenario 5: Cascading messages - MediatR requires manual publish of events
68-
[FoundatioIgnore]
6963
public class MediatRCreateOrderHandler : IRequestHandler<CreateOrder, Order>
7064
{
7165
private readonly global::MediatR.IMediator _mediator;
@@ -84,28 +78,25 @@ public async Task<Order> Handle(CreateOrder request, CancellationToken cancellat
8478
}
8579

8680
// Handlers for the cascaded OrderCreatedEvent
87-
[FoundatioIgnore]
8881
public class MediatROrderCreatedHandler1 : INotificationHandler<OrderCreatedEvent>
8982
{
90-
public async Task Handle(OrderCreatedEvent notification, CancellationToken cancellationToken)
83+
public Task Handle(OrderCreatedEvent notification, CancellationToken cancellationToken)
9184
{
92-
// First handler for order created event
93-
await Task.CompletedTask;
85+
// First handler for order created event - no async state machine
86+
return Task.CompletedTask;
9487
}
9588
}
9689

97-
[FoundatioIgnore]
9890
public class MediatROrderCreatedHandler2 : INotificationHandler<OrderCreatedEvent>
9991
{
100-
public async Task Handle(OrderCreatedEvent notification, CancellationToken cancellationToken)
92+
public Task Handle(OrderCreatedEvent notification, CancellationToken cancellationToken)
10193
{
102-
// Second handler for order created event
103-
await Task.CompletedTask;
94+
// Second handler for order created event - no async state machine
95+
return Task.CompletedTask;
10496
}
10597
}
10698

10799
// MediatR pipeline behavior for timing (equivalent to Foundatio's middleware)
108-
[FoundatioIgnore]
109100
public class TimingBehavior<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse>
110101
where TRequest : notnull
111102
{
@@ -125,7 +116,6 @@ public async Task<TResponse> Handle(TRequest request, RequestHandlerDelegate<TRe
125116
}
126117

127118
// Scenario 6: Short-circuit handler - MediatR uses IPipelineBehavior to short-circuit
128-
[FoundatioIgnore]
129119
public class MediatRShortCircuitHandler : IRequestHandler<GetCachedOrder, Order>
130120
{
131121
public Task<Order> Handle(GetCachedOrder request, CancellationToken cancellationToken)
@@ -136,7 +126,6 @@ public Task<Order> Handle(GetCachedOrder request, CancellationToken cancellation
136126
}
137127

138128
// MediatR short-circuit behavior - returns cached value without calling handler
139-
[FoundatioIgnore]
140129
public class ShortCircuitBehavior : IPipelineBehavior<GetCachedOrder, Order>
141130
{
142131
private static readonly Order _cachedOrder = new(999, 49.99m, DateTime.UtcNow);

benchmarks/Foundatio.Mediator.Benchmarks/Handlers/MediatorNet/MediatorNetHandlers.cs

Lines changed: 0 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -6,29 +6,21 @@ namespace Foundatio.Mediator.Benchmarks.Handlers.MediatorNet;
66
// Message types for Mediator.SourceGenerator
77
// Note: We define separate message types because Mediator uses different interfaces than MediatR
88

9-
[FoundatioIgnore]
109
public record MediatorNetPingCommand(string Id) : MediatorLib.ICommand;
1110

12-
[FoundatioIgnore]
1311
public record MediatorNetGetOrder(int Id) : MediatorLib.IQuery<Order>;
1412

15-
[FoundatioIgnore]
1613
public record MediatorNetGetFullQuery(int Id) : MediatorLib.IQuery<Order>;
1714

18-
[FoundatioIgnore]
1915
public record MediatorNetUserRegisteredEvent(string UserId, string Email) : MediatorLib.INotification;
2016

21-
[FoundatioIgnore]
2217
public record MediatorNetCreateOrder(int CustomerId, decimal Amount) : MediatorLib.IRequest<Order>;
2318

24-
[FoundatioIgnore]
2519
public record MediatorNetOrderCreatedEvent(int OrderId, int CustomerId) : MediatorLib.INotification;
2620

27-
[FoundatioIgnore]
2821
public record MediatorNetGetCachedOrder(int Id) : MediatorLib.IQuery<Order>;
2922

3023
// Scenario 1: Command handler (InvokeAsync without response)
31-
[FoundatioIgnore]
3224
public class MediatorNetCommandHandler : MediatorLib.ICommandHandler<MediatorNetPingCommand>
3325
{
3426
public ValueTask<MediatorLib.Unit> Handle(MediatorNetPingCommand command, CancellationToken cancellationToken)
@@ -39,7 +31,6 @@ public class MediatorNetCommandHandler : MediatorLib.ICommandHandler<MediatorNet
3931
}
4032

4133
// Scenario 2: Query handler (InvokeAsync<T>) - No DI for baseline comparison
42-
[FoundatioIgnore]
4334
public class MediatorNetQueryHandler : MediatorLib.IQueryHandler<MediatorNetGetOrder, Order>
4435
{
4536
public ValueTask<Order> Handle(MediatorNetGetOrder query, CancellationToken cancellationToken)
@@ -49,7 +40,6 @@ public ValueTask<Order> Handle(MediatorNetGetOrder query, CancellationToken canc
4940
}
5041

5142
// Scenario 3: Notification handlers (PublishAsync with multiple handlers)
52-
[FoundatioIgnore]
5343
public class MediatorNetEventHandler : MediatorLib.INotificationHandler<MediatorNetUserRegisteredEvent>
5444
{
5545
public ValueTask Handle(MediatorNetUserRegisteredEvent notification, CancellationToken cancellationToken)
@@ -59,7 +49,6 @@ public ValueTask Handle(MediatorNetUserRegisteredEvent notification, Cancellatio
5949
}
6050
}
6151

62-
[FoundatioIgnore]
6352
public class MediatorNetEventHandler2 : MediatorLib.INotificationHandler<MediatorNetUserRegisteredEvent>
6453
{
6554
public ValueTask Handle(MediatorNetUserRegisteredEvent notification, CancellationToken cancellationToken)
@@ -70,7 +59,6 @@ public ValueTask Handle(MediatorNetUserRegisteredEvent notification, Cancellatio
7059
}
7160

7261
// Scenario 4: Query handler with dependency injection
73-
[FoundatioIgnore]
7462
public class MediatorNetFullQueryHandler : MediatorLib.IQueryHandler<MediatorNetGetFullQuery, Order>
7563
{
7664
private readonly IOrderService _orderService;
@@ -87,7 +75,6 @@ public async ValueTask<Order> Handle(MediatorNetGetFullQuery query, Cancellation
8775
}
8876

8977
// Scenario 5: Cascading messages - MediatorNet requires manual publish of events
90-
[FoundatioIgnore]
9178
public class MediatorNetCreateOrderHandler : MediatorLib.IRequestHandler<MediatorNetCreateOrder, Order>
9279
{
9380
private readonly MediatorLib.IMediator _mediator;
@@ -106,7 +93,6 @@ public async ValueTask<Order> Handle(MediatorNetCreateOrder request, Cancellatio
10693
}
10794

10895
// Handlers for the cascaded OrderCreatedEvent
109-
[FoundatioIgnore]
11096
public class MediatorNetOrderCreatedHandler1 : MediatorLib.INotificationHandler<MediatorNetOrderCreatedEvent>
11197
{
11298
public ValueTask Handle(MediatorNetOrderCreatedEvent notification, CancellationToken cancellationToken)
@@ -116,7 +102,6 @@ public ValueTask Handle(MediatorNetOrderCreatedEvent notification, CancellationT
116102
}
117103
}
118104

119-
[FoundatioIgnore]
120105
public class MediatorNetOrderCreatedHandler2 : MediatorLib.INotificationHandler<MediatorNetOrderCreatedEvent>
121106
{
122107
public ValueTask Handle(MediatorNetOrderCreatedEvent notification, CancellationToken cancellationToken)
@@ -127,7 +112,6 @@ public ValueTask Handle(MediatorNetOrderCreatedEvent notification, CancellationT
127112
}
128113

129114
// Scenario 6: Short-circuit handler - MediatorNet uses IPipelineBehavior to short-circuit
130-
[FoundatioIgnore]
131115
public class MediatorNetShortCircuitHandler : MediatorLib.IQueryHandler<MediatorNetGetCachedOrder, Order>
132116
{
133117
public ValueTask<Order> Handle(MediatorNetGetCachedOrder query, CancellationToken cancellationToken)
@@ -138,7 +122,6 @@ public ValueTask<Order> Handle(MediatorNetGetCachedOrder query, CancellationToke
138122
}
139123

140124
// MediatorNet short-circuit behavior - returns cached value without calling handler
141-
[FoundatioIgnore]
142125
public class MediatorNetShortCircuitBehavior : MediatorLib.IPipelineBehavior<MediatorNetGetCachedOrder, Order>
143126
{
144127
private static readonly Order _cachedOrder = new(999, 49.99m, DateTime.UtcNow);

0 commit comments

Comments
 (0)