diff --git a/Mediator.sln b/Mediator.sln
index d7c774c..a81a62a 100644
--- a/Mediator.sln
+++ b/Mediator.sln
@@ -72,7 +72,11 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Mediator.SourceGenerator.Im
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Mediator.SourceGenerator.Roslyn40.Tests", "test\Mediator.SourceGenerator.Roslyn40.Tests\Mediator.SourceGenerator.Roslyn40.Tests.csproj", "{E4EE80C5-179C-4483-9E91-3107B3E1CD5A}"
EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Mediator.SmokeTestConsole", "test\Mediator.SmokeTestConsole\Mediator.SmokeTestConsole.csproj", "{65D8C0F8-E4CF-452D-86FB-482FE63C7C89}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Mediator.SmokeTestConsole", "test\Mediator.SmokeTestConsole\Mediator.SmokeTestConsole.csproj", "{65D8C0F8-E4CF-452D-86FB-482FE63C7C89}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Mediator.MemAllocationTests", "test\Mediator.MemAllocationTests\Mediator.MemAllocationTests.csproj", "{EECFACC8-E841-4EF5-A1D7-A64C84B7DBCD}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Mediator.Tests.Common", "test\Mediator.Tests.Common\Mediator.Tests.Common.csproj", "{32DC3B96-7E6E-40B0-AA12-40656B2D0381}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
@@ -336,6 +340,30 @@ Global
{65D8C0F8-E4CF-452D-86FB-482FE63C7C89}.Release|x64.Build.0 = Release|Any CPU
{65D8C0F8-E4CF-452D-86FB-482FE63C7C89}.Release|x86.ActiveCfg = Release|Any CPU
{65D8C0F8-E4CF-452D-86FB-482FE63C7C89}.Release|x86.Build.0 = Release|Any CPU
+ {EECFACC8-E841-4EF5-A1D7-A64C84B7DBCD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {EECFACC8-E841-4EF5-A1D7-A64C84B7DBCD}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {EECFACC8-E841-4EF5-A1D7-A64C84B7DBCD}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {EECFACC8-E841-4EF5-A1D7-A64C84B7DBCD}.Debug|x64.Build.0 = Debug|Any CPU
+ {EECFACC8-E841-4EF5-A1D7-A64C84B7DBCD}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {EECFACC8-E841-4EF5-A1D7-A64C84B7DBCD}.Debug|x86.Build.0 = Debug|Any CPU
+ {EECFACC8-E841-4EF5-A1D7-A64C84B7DBCD}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {EECFACC8-E841-4EF5-A1D7-A64C84B7DBCD}.Release|Any CPU.Build.0 = Release|Any CPU
+ {EECFACC8-E841-4EF5-A1D7-A64C84B7DBCD}.Release|x64.ActiveCfg = Release|Any CPU
+ {EECFACC8-E841-4EF5-A1D7-A64C84B7DBCD}.Release|x64.Build.0 = Release|Any CPU
+ {EECFACC8-E841-4EF5-A1D7-A64C84B7DBCD}.Release|x86.ActiveCfg = Release|Any CPU
+ {EECFACC8-E841-4EF5-A1D7-A64C84B7DBCD}.Release|x86.Build.0 = Release|Any CPU
+ {32DC3B96-7E6E-40B0-AA12-40656B2D0381}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {32DC3B96-7E6E-40B0-AA12-40656B2D0381}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {32DC3B96-7E6E-40B0-AA12-40656B2D0381}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {32DC3B96-7E6E-40B0-AA12-40656B2D0381}.Debug|x64.Build.0 = Debug|Any CPU
+ {32DC3B96-7E6E-40B0-AA12-40656B2D0381}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {32DC3B96-7E6E-40B0-AA12-40656B2D0381}.Debug|x86.Build.0 = Debug|Any CPU
+ {32DC3B96-7E6E-40B0-AA12-40656B2D0381}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {32DC3B96-7E6E-40B0-AA12-40656B2D0381}.Release|Any CPU.Build.0 = Release|Any CPU
+ {32DC3B96-7E6E-40B0-AA12-40656B2D0381}.Release|x64.ActiveCfg = Release|Any CPU
+ {32DC3B96-7E6E-40B0-AA12-40656B2D0381}.Release|x64.Build.0 = Release|Any CPU
+ {32DC3B96-7E6E-40B0-AA12-40656B2D0381}.Release|x86.ActiveCfg = Release|Any CPU
+ {32DC3B96-7E6E-40B0-AA12-40656B2D0381}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -363,6 +391,8 @@ Global
{22984D49-8DDF-4263-8375-D018643E44B2} = {438C928E-FBB5-45B2-9CD2-7ED3341AC75E}
{E4EE80C5-179C-4483-9E91-3107B3E1CD5A} = {ED1809FC-733B-4D9E-8FEE-70D3BB0BBD84}
{65D8C0F8-E4CF-452D-86FB-482FE63C7C89} = {ED1809FC-733B-4D9E-8FEE-70D3BB0BBD84}
+ {EECFACC8-E841-4EF5-A1D7-A64C84B7DBCD} = {ED1809FC-733B-4D9E-8FEE-70D3BB0BBD84}
+ {32DC3B96-7E6E-40B0-AA12-40656B2D0381} = {ED1809FC-733B-4D9E-8FEE-70D3BB0BBD84}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {D45B5457-4190-49B6-BF89-7FA5F4C8ABE2}
diff --git a/test/Mediator.MemAllocationTests/Mediator.MemAllocationTests.csproj b/test/Mediator.MemAllocationTests/Mediator.MemAllocationTests.csproj
new file mode 100644
index 0000000..2cda3fd
--- /dev/null
+++ b/test/Mediator.MemAllocationTests/Mediator.MemAllocationTests.csproj
@@ -0,0 +1,39 @@
+
+
+
+ net6.0
+
+ false
+ true
+ true
+
+
+
+
+
+
+
+
+
+
+
+
+
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+ all
+
+
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+ all
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/test/Mediator.MemAllocationTests/NonParallelCollectionDefinitionClass.cs b/test/Mediator.MemAllocationTests/NonParallelCollectionDefinitionClass.cs
new file mode 100644
index 0000000..1ba952c
--- /dev/null
+++ b/test/Mediator.MemAllocationTests/NonParallelCollectionDefinitionClass.cs
@@ -0,0 +1,6 @@
+namespace Mediator.Tests;
+
+[CollectionDefinition("Non-Parallel", DisableParallelization = true)]
+public class NonParallelCollectionDefinitionClass
+{
+}
diff --git a/test/Mediator.MemAllocationTests/NotificationTests.cs b/test/Mediator.MemAllocationTests/NotificationTests.cs
new file mode 100644
index 0000000..52c7eb6
--- /dev/null
+++ b/test/Mediator.MemAllocationTests/NotificationTests.cs
@@ -0,0 +1,32 @@
+using Mediator.Tests.Common;
+using Microsoft.Extensions.DependencyInjection;
+
+namespace Mediator.MemAllocationTests;
+
+[Collection("Non-Parallel")]
+public class NotificationTests
+{
+ [Fact]
+ public void Test_Notification_Handler()
+ {
+ var services = new ServiceCollection();
+
+ services.AddMediator();
+
+ using var sp = services.BuildServiceProvider(validateScopes: true);
+ var mediator = sp.GetRequiredService();
+
+ var id = Guid.NewGuid();
+ var notification = new SomeNotificationMemAllocTracking(id);
+
+ // Ensure everything is cached
+ mediator.Publish(notification); // Everything returns sync
+
+ var beforeBytes = Allocations.GetCurrentThreadAllocatedBytes();
+ mediator.Publish(notification); // Everything returns sync
+ var afterBytes = Allocations.GetCurrentThreadAllocatedBytes();
+
+ // TODO: why 40? benchmarkdotnet reports 0 alloc for this case
+ Assert.Equal(40, afterBytes - beforeBytes);
+ }
+}
diff --git a/test/Mediator.MemAllocationTests/RequestTests.cs b/test/Mediator.MemAllocationTests/RequestTests.cs
new file mode 100644
index 0000000..92216a3
--- /dev/null
+++ b/test/Mediator.MemAllocationTests/RequestTests.cs
@@ -0,0 +1,94 @@
+using JetBrains.dotMemoryUnit;
+using JetBrains.dotMemoryUnit.Kernel;
+using Mediator.Tests.Common;
+using Microsoft.Extensions.DependencyInjection;
+using Xunit.Abstractions;
+
+namespace Mediator.MemAllocationTests;
+
+[Collection("Non-Parallel")]
+public class RequestTests
+{
+ private readonly ITestOutputHelper _output;
+
+ public RequestTests(ITestOutputHelper output)
+ {
+ _output = output;
+ DotMemoryUnitTestOutput.SetOutputMethod(output.WriteLine);
+ }
+
+ ///
+ /// To run these tests
+ /// dotMemoryUnit.exe "$((Get-Command dotnet.exe).Path)" -- test "./test/Mediator.MemAllocationTests/"
+ /// TODO: find a way to filter out dotMemoryUnit's own mem allocations from report...
+ ///
+ ///
+ [DotMemoryUnit(CollectAllocations = true)]
+ [Fact]
+ public async Task Test_Request_Handler_dotMemory()
+ {
+ if (!dotMemoryApi.IsEnabled)
+ return;
+
+ var services = new ServiceCollection();
+
+ services.AddMediator();
+
+ await using var sp = services.BuildServiceProvider(validateScopes: true);
+ var mediator = sp.GetRequiredService();
+
+ var id = Guid.NewGuid();
+ var request = new SomeRequestMemAllocTracking(id);
+
+ // Ensure everything is cached
+ await mediator.Send(request);
+ GC.Collect();
+ GC.WaitForPendingFinalizers();
+ GC.WaitForFullGCComplete();
+ GC.Collect();
+
+ var checkpoint = dotMemory.Check();
+ var beforeBytes = GC.GetAllocatedBytesForCurrentThread();
+ await mediator.Send(request);
+ var afterBytes = GC.GetAllocatedBytesForCurrentThread();
+
+ Assert.Equal(0, afterBytes - beforeBytes);
+ dotMemory.Check(memory =>
+ {
+ var traffic = memory.GetDifference(checkpoint);
+ var newObjects = traffic.GetNewObjects();
+ foreach (var obj in newObjects.GroupByType())
+ _output.WriteLine($"Allocations for {obj.TypeFullyQualifiedName}: {obj.SizeInBytes} bytes");
+
+ //_output.WriteLine($"Allocated: {traffic.AllocatedMemory.SizeInBytes}");
+ //foreach (var obj in traffic.GroupByType())
+ // _output.WriteLine($"Allocations for {obj.TypeFullyQualifiedName}: {obj.AllocatedMemoryInfo.SizeInBytes}");
+
+ Assert.Equal(0, traffic.GetNewObjects().ObjectsCount);
+ });
+ }
+
+
+ [Fact]
+ public void Test_Request_Handler()
+ {
+ var services = new ServiceCollection();
+
+ services.AddMediator();
+
+ using var sp = services.BuildServiceProvider(validateScopes: true);
+ var mediator = sp.GetRequiredService();
+
+ var id = Guid.NewGuid();
+ var request = new SomeRequestMemAllocTracking(id);
+
+ // Ensure everything is cached
+ mediator.Send(request); // Everything returns sync
+
+ var beforeBytes = Allocations.GetCurrentThreadAllocatedBytes();
+ mediator.Send(request); // Everything returns sync
+ var afterBytes = Allocations.GetCurrentThreadAllocatedBytes();
+
+ Assert.Equal(0, afterBytes - beforeBytes);
+ }
+}
diff --git a/test/Mediator.MemAllocationTests/SomeNotificationMemAllocTracking.cs b/test/Mediator.MemAllocationTests/SomeNotificationMemAllocTracking.cs
new file mode 100644
index 0000000..acde029
--- /dev/null
+++ b/test/Mediator.MemAllocationTests/SomeNotificationMemAllocTracking.cs
@@ -0,0 +1,8 @@
+namespace Mediator.MemAllocationTests;
+
+public sealed record SomeNotificationMemAllocTracking(Guid Id) : INotification;
+
+public sealed class SomeNotificationMemAllocTrackingHandler : INotificationHandler
+{
+ public ValueTask Handle(SomeNotificationMemAllocTracking notification, CancellationToken cancellationToken) => default;
+}
diff --git a/test/Mediator.MemAllocationTests/SomeRequestMemAllocTracking.cs b/test/Mediator.MemAllocationTests/SomeRequestMemAllocTracking.cs
new file mode 100644
index 0000000..863a74e
--- /dev/null
+++ b/test/Mediator.MemAllocationTests/SomeRequestMemAllocTracking.cs
@@ -0,0 +1,8 @@
+namespace Mediator.MemAllocationTests;
+
+public sealed record SomeRequestMemAllocTracking(Guid Id) : IRequest;
+
+public sealed class SomeRequestMemAllocTrackingHandler : IRequestHandler
+{
+ public ValueTask Handle(SomeRequestMemAllocTracking request, CancellationToken cancellationToken) => default;
+}
diff --git a/test/Mediator.Tests.Common/Fixture.cs b/test/Mediator.Tests.Common/Fixture.cs
new file mode 100644
index 0000000..647aa42
--- /dev/null
+++ b/test/Mediator.Tests.Common/Fixture.cs
@@ -0,0 +1,22 @@
+namespace Mediator.Tests.Common;
+
+public static class Allocations
+{
+ public static long GetCurrentThreadAllocatedBytes()
+ {
+ // Run before and after message sending to test allocations.
+ // Stolen from https://github.com/dotnet/BenchmarkDotNet/blob/4bd433d85fff4fb6ba8c4f8df3e685ad669e2519/src/BenchmarkDotNet/Engines/GcStats.cs#L132
+ // There is a reason GC.Collect is run first
+ GC.Collect();
+ return GC.GetAllocatedBytesForCurrentThread();
+ }
+
+ public static long GetCurrentAllocatedBytes()
+ {
+ // Run before and after message sending to test allocations.
+ // Stolen from https://github.com/dotnet/BenchmarkDotNet/blob/4bd433d85fff4fb6ba8c4f8df3e685ad669e2519/src/BenchmarkDotNet/Engines/GcStats.cs#L132
+ // There is a reason GC.Collect is run first
+ GC.Collect();
+ return GC.GetTotalAllocatedBytes(true);
+ }
+}
diff --git a/test/Mediator.Tests.Common/Mediator.Tests.Common.csproj b/test/Mediator.Tests.Common/Mediator.Tests.Common.csproj
new file mode 100644
index 0000000..373d9bc
--- /dev/null
+++ b/test/Mediator.Tests.Common/Mediator.Tests.Common.csproj
@@ -0,0 +1,15 @@
+
+
+
+ net6.0
+
+
+
+
+
+
+
+
+
+
+
diff --git a/test/Mediator.Tests/Fixture.cs b/test/Mediator.Tests/Fixture.cs
index 39a1658..f8d338c 100644
--- a/test/Mediator.Tests/Fixture.cs
+++ b/test/Mediator.Tests/Fixture.cs
@@ -49,7 +49,6 @@ public static (IServiceProvider sp, IMediator mediator) GetMediatorCustomContain
return (sp, mediator!);
}
-
private sealed class CustomServiceProvider : IServiceProvider
{
private readonly IServiceCollection _services;
diff --git a/test/Mediator.Tests/Mediator.Tests.csproj b/test/Mediator.Tests/Mediator.Tests.csproj
index f32fde3..948ee8e 100644
--- a/test/Mediator.Tests/Mediator.Tests.csproj
+++ b/test/Mediator.Tests/Mediator.Tests.csproj
@@ -32,6 +32,7 @@
+
diff --git a/test/Mediator.Tests/NonParallelCollectionDefinitionClass.cs b/test/Mediator.Tests/NonParallelCollectionDefinitionClass.cs
new file mode 100644
index 0000000..1ba952c
--- /dev/null
+++ b/test/Mediator.Tests/NonParallelCollectionDefinitionClass.cs
@@ -0,0 +1,6 @@
+namespace Mediator.Tests;
+
+[CollectionDefinition("Non-Parallel", DisableParallelization = true)]
+public class NonParallelCollectionDefinitionClass
+{
+}