Skip to content

Commit

Permalink
Parallel notifications, correct ordering for switch statement cases (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
martinothamar authored Apr 7, 2024
1 parent 54f96f6 commit 1858bbc
Show file tree
Hide file tree
Showing 163 changed files with 9,010 additions and 1,043 deletions.
53 changes: 50 additions & 3 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,50 @@ on:

jobs:
build:
runs-on: ubuntu-latest
strategy:
matrix:
lifetime: [Singleton, Scoped, Transient]
publisher: [ForeachAwait, TaskWhenAll]

env:
NUGET_PACKAGES: ${{ github.workspace }}/.nuget/packages
steps:
- uses: actions/checkout@v2
with:
fetch-depth: 0 # depth is needed for nbgv
- uses: dotnet/nbgv@master
with:
setAllVars: true

- name: Setup .NET
uses: actions/setup-dotnet@v1
with:
dotnet-version: '8.0.x'
include-prerelease: false

- uses: actions/cache@v3
with:
path: ${{ github.workspace }}/.nuget/packages
key: ${{ runner.os }}-nuget-${{ hashFiles('**/*.csproj', '**/Directory.*.props', 'NuGet.config') }}
restore-keys: |
${{ runner.os }}-nuget-
- name: Restore dependencies
run: dotnet restore

- name: Build
run: dotnet build --no-restore -p:ExtraDefineConstants=\"Mediator_Lifetime_${{ matrix.lifetime }}%3BMediator_Publisher_${{ matrix.publisher }}\"

- name: Test
run: dotnet test --no-restore --no-build --logger "console;verbosity=detailed"

publish:
needs: build
runs-on: ubuntu-latest

env:
NUGET_PACKAGES: ${{ github.workspace }}/.nuget/packages
steps:
- uses: actions/checkout@v2
with:
Expand All @@ -33,21 +74,27 @@ jobs:
dotnet-version: '8.0.x'
include-prerelease: false

- uses: actions/cache@v3
with:
path: ${{ github.workspace }}/.nuget/packages
key: ${{ runner.os }}-nuget-${{ hashFiles('**/*.csproj', '**/Directory.*.props', 'NuGet.config') }}
restore-keys: |
${{ runner.os }}-nuget-
- name: Restore dependencies
run: dotnet restore

- name: Build
run: dotnet build --no-restore -c Release

- name: Test
run: dotnet test --no-restore --verbosity normal -c Release
run: dotnet test --no-restore --no-build -c Release --logger "console;verbosity=detailed"

- name: Pack
if: ${{ success() && !github.base_ref }}
run: |
dotnet pack src/Mediator/Mediator.csproj --no-restore --no-build --verbosity normal -c Release -o artifacts/ && \
dotnet pack src/Mediator.SourceGenerator/Mediator.SourceGenerator.csproj --no-restore --no-build --verbosity normal -c Release -o artifacts/
- name: Push to NuGet
if: ${{ success() && !github.base_ref }}
if: ${{ success() && github.event_name != 'pull_request' }}
run: dotnet nuget push artifacts/**.nupkg -s https://api.nuget.org/v3/index.json --api-key ${{secrets.NUGET_API_KEY}}
3 changes: 3 additions & 0 deletions Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
<!-- Other libs -->
<PackageVersion Include="MediatR" Version="12.2.0" />
<PackageVersion Include="MessagePipe" Version="1.7.4" />
<PackageVersion Include="PolySharp" Version="1.14.1" />

<!-- BCL -->
<PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="$(DotNetVersion)" />
Expand All @@ -18,6 +19,8 @@
<PackageVersion Include="Microsoft.Extensions.Logging" Version="$(DotNetVersion)" />
<PackageVersion Include="System.Configuration.ConfigurationManager" Version="$(DotNetVersion)" />
<PackageVersion Include="System.Collections.Immutable" Version="$(DotNetVersion)" />
<PackageVersion Include="Microsoft.Bcl.AsyncInterfaces" Version="$(DotNetVersion)" />
<PackageVersion Include="System.Runtime.CompilerServices.Unsafe" Version="6.0.0" />
<PackageVersion Include="System.Linq.Async" Version="6.0.1" />

<!-- Roslyn / MSBuild -->
Expand Down
18 changes: 17 additions & 1 deletion Justfile
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,21 @@ clean:
build:
dotnet build Mediator.sln

_test constants:
dotnet clean
dotnet build --no-restore -p:ExtraDefineConstants=\"{{constants}}\"
dotnet test --no-restore --no-build --logger "console;verbosity=detailed"
test:
dotnet test Mediator.sln
dotnet restore
just -f {{ justfile() }} _test 'Mediator_Lifetime_Singleton%3BMediator_Publisher_ForeachAwait'
just -f {{ justfile() }} _test 'Mediator_Lifetime_Scoped%3BMediator_Publisher_ForeachAwait'
just -f {{ justfile() }} _test 'Mediator_Lifetime_Transient%3BMediator_Publisher_ForeachAwait'
just -f {{ justfile() }} _test 'Mediator_Lifetime_Singleton%3BMediator_Publisher_TaskWhenAll'
just -f {{ justfile() }} _test 'Mediator_Lifetime_Scoped%3BMediator_Publisher_TaskWhenAll'
just -f {{ justfile() }} _test 'Mediator_Lifetime_Transient%3BMediator_Publisher_TaskWhenAll'
dotnet clean
dotnet build --no-restore
dotnet test --no-restore --no-build --logger "console;verbosity=detailed"
48 changes: 18 additions & 30 deletions Mediator.sln
Original file line number Diff line number Diff line change
Expand Up @@ -61,10 +61,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "_files", "_files", "{FDAEEF
version.json = version.json
EndProjectSection
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Mediator.Tests.TransientLifetime", "test\Mediator.Tests.TransientLifetime\Mediator.Tests.TransientLifetime.csproj", "{4C18FD08-BCE4-4C6A-9D84-9A81C4561586}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Mediator.Tests.ScopedLifetime", "test\Mediator.Tests.ScopedLifetime\Mediator.Tests.ScopedLifetime.csproj", "{6FC1A2EC-7178-4A3E-93DF-1ABDF09F4943}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Streaming", "samples\basic\Streaming\Streaming.csproj", "{D24BCAD4-0C17-40D0-8ADC-B8A5E8700047}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ASPNET_Core", "samples\apps\ASPNET_Core\ASPNET_Core.csproj", "{35D4B136-E164-48A3-ADC3-E22237CCDC9C}"
Expand Down Expand Up @@ -101,6 +97,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "InternalMessages.Applicatio
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "InternalMessages.Domain", "samples\apps\InternalMessages\InternalMessages.Domain\InternalMessages.Domain.csproj", "{1A16060A-3393-4404-A3AC-63E10B3648DE}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "basic", "basic", "{686F96A2-0D44-4A5B-9A7C-78608D95E5DB}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NotificationPublisher", "samples\basic\NotificationPublisher\NotificationPublisher.csproj", "{FF68E713-7FA9-42F4-8D0F-81A4387BECCD}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -243,30 +243,6 @@ Global
{B9E8DA69-C6FA-446F-B949-EF48856551D8}.Release|x64.Build.0 = Release|Any CPU
{B9E8DA69-C6FA-446F-B949-EF48856551D8}.Release|x86.ActiveCfg = Release|Any CPU
{B9E8DA69-C6FA-446F-B949-EF48856551D8}.Release|x86.Build.0 = Release|Any CPU
{4C18FD08-BCE4-4C6A-9D84-9A81C4561586}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{4C18FD08-BCE4-4C6A-9D84-9A81C4561586}.Debug|Any CPU.Build.0 = Debug|Any CPU
{4C18FD08-BCE4-4C6A-9D84-9A81C4561586}.Debug|x64.ActiveCfg = Debug|Any CPU
{4C18FD08-BCE4-4C6A-9D84-9A81C4561586}.Debug|x64.Build.0 = Debug|Any CPU
{4C18FD08-BCE4-4C6A-9D84-9A81C4561586}.Debug|x86.ActiveCfg = Debug|Any CPU
{4C18FD08-BCE4-4C6A-9D84-9A81C4561586}.Debug|x86.Build.0 = Debug|Any CPU
{4C18FD08-BCE4-4C6A-9D84-9A81C4561586}.Release|Any CPU.ActiveCfg = Release|Any CPU
{4C18FD08-BCE4-4C6A-9D84-9A81C4561586}.Release|Any CPU.Build.0 = Release|Any CPU
{4C18FD08-BCE4-4C6A-9D84-9A81C4561586}.Release|x64.ActiveCfg = Release|Any CPU
{4C18FD08-BCE4-4C6A-9D84-9A81C4561586}.Release|x64.Build.0 = Release|Any CPU
{4C18FD08-BCE4-4C6A-9D84-9A81C4561586}.Release|x86.ActiveCfg = Release|Any CPU
{4C18FD08-BCE4-4C6A-9D84-9A81C4561586}.Release|x86.Build.0 = Release|Any CPU
{6FC1A2EC-7178-4A3E-93DF-1ABDF09F4943}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{6FC1A2EC-7178-4A3E-93DF-1ABDF09F4943}.Debug|Any CPU.Build.0 = Debug|Any CPU
{6FC1A2EC-7178-4A3E-93DF-1ABDF09F4943}.Debug|x64.ActiveCfg = Debug|Any CPU
{6FC1A2EC-7178-4A3E-93DF-1ABDF09F4943}.Debug|x64.Build.0 = Debug|Any CPU
{6FC1A2EC-7178-4A3E-93DF-1ABDF09F4943}.Debug|x86.ActiveCfg = Debug|Any CPU
{6FC1A2EC-7178-4A3E-93DF-1ABDF09F4943}.Debug|x86.Build.0 = Debug|Any CPU
{6FC1A2EC-7178-4A3E-93DF-1ABDF09F4943}.Release|Any CPU.ActiveCfg = Release|Any CPU
{6FC1A2EC-7178-4A3E-93DF-1ABDF09F4943}.Release|Any CPU.Build.0 = Release|Any CPU
{6FC1A2EC-7178-4A3E-93DF-1ABDF09F4943}.Release|x64.ActiveCfg = Release|Any CPU
{6FC1A2EC-7178-4A3E-93DF-1ABDF09F4943}.Release|x64.Build.0 = Release|Any CPU
{6FC1A2EC-7178-4A3E-93DF-1ABDF09F4943}.Release|x86.ActiveCfg = Release|Any CPU
{6FC1A2EC-7178-4A3E-93DF-1ABDF09F4943}.Release|x86.Build.0 = Release|Any CPU
{D24BCAD4-0C17-40D0-8ADC-B8A5E8700047}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{D24BCAD4-0C17-40D0-8ADC-B8A5E8700047}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D24BCAD4-0C17-40D0-8ADC-B8A5E8700047}.Debug|x64.ActiveCfg = Debug|Any CPU
Expand Down Expand Up @@ -435,6 +411,18 @@ Global
{1A16060A-3393-4404-A3AC-63E10B3648DE}.Release|x64.Build.0 = Release|Any CPU
{1A16060A-3393-4404-A3AC-63E10B3648DE}.Release|x86.ActiveCfg = Release|Any CPU
{1A16060A-3393-4404-A3AC-63E10B3648DE}.Release|x86.Build.0 = Release|Any CPU
{FF68E713-7FA9-42F4-8D0F-81A4387BECCD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{FF68E713-7FA9-42F4-8D0F-81A4387BECCD}.Debug|Any CPU.Build.0 = Debug|Any CPU
{FF68E713-7FA9-42F4-8D0F-81A4387BECCD}.Debug|x64.ActiveCfg = Debug|Any CPU
{FF68E713-7FA9-42F4-8D0F-81A4387BECCD}.Debug|x64.Build.0 = Debug|Any CPU
{FF68E713-7FA9-42F4-8D0F-81A4387BECCD}.Debug|x86.ActiveCfg = Debug|Any CPU
{FF68E713-7FA9-42F4-8D0F-81A4387BECCD}.Debug|x86.Build.0 = Debug|Any CPU
{FF68E713-7FA9-42F4-8D0F-81A4387BECCD}.Release|Any CPU.ActiveCfg = Release|Any CPU
{FF68E713-7FA9-42F4-8D0F-81A4387BECCD}.Release|Any CPU.Build.0 = Release|Any CPU
{FF68E713-7FA9-42F4-8D0F-81A4387BECCD}.Release|x64.ActiveCfg = Release|Any CPU
{FF68E713-7FA9-42F4-8D0F-81A4387BECCD}.Release|x64.Build.0 = Release|Any CPU
{FF68E713-7FA9-42F4-8D0F-81A4387BECCD}.Release|x86.ActiveCfg = Release|Any CPU
{FF68E713-7FA9-42F4-8D0F-81A4387BECCD}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand All @@ -452,8 +440,6 @@ Global
{45FC79C1-76EB-4B30-B54F-DD1762BA15EE} = {D3569CDD-7E19-429E-B9AD-75CC05F6C4AA}
{CE5A582D-3CC6-44D7-AF3F-CD741BA53833} = {B0120C11-5024-491A-B3AD-821E1D2CD8F2}
{B9E8DA69-C6FA-446F-B949-EF48856551D8} = {D3569CDD-7E19-429E-B9AD-75CC05F6C4AA}
{4C18FD08-BCE4-4C6A-9D84-9A81C4561586} = {ED1809FC-733B-4D9E-8FEE-70D3BB0BBD84}
{6FC1A2EC-7178-4A3E-93DF-1ABDF09F4943} = {ED1809FC-733B-4D9E-8FEE-70D3BB0BBD84}
{D24BCAD4-0C17-40D0-8ADC-B8A5E8700047} = {D3569CDD-7E19-429E-B9AD-75CC05F6C4AA}
{35D4B136-E164-48A3-ADC3-E22237CCDC9C} = {D3569CDD-7E19-429E-B9AD-75CC05F6C4AA}
{E4EE80C5-179C-4483-9E91-3107B3E1CD5A} = {ED1809FC-733B-4D9E-8FEE-70D3BB0BBD84}
Expand All @@ -470,6 +456,8 @@ Global
{10E7F076-1B64-4DF6-9FA0-399BBD87F42F} = {D7662382-63B6-4E3D-A6CE-8EC19E473265}
{B4EE2FF6-3D88-45B5-9F43-9BD2474F2181} = {D7662382-63B6-4E3D-A6CE-8EC19E473265}
{1A16060A-3393-4404-A3AC-63E10B3648DE} = {D7662382-63B6-4E3D-A6CE-8EC19E473265}
{686F96A2-0D44-4A5B-9A7C-78608D95E5DB} = {D3569CDD-7E19-429E-B9AD-75CC05F6C4AA}
{FF68E713-7FA9-42F4-8D0F-81A4387BECCD} = {686F96A2-0D44-4A5B-9A7C-78608D95E5DB}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {D45B5457-4190-49B6-BF89-7FA5F4C8ABE2}
Expand Down
47 changes: 45 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,8 @@ See this great video by [@Elfocrash / Nick Chapsas](https://github.com/Elfocrash
- [4.6. Use notifications](#46-use-notifications)
- [4.7. Polymorphic dispatch with notification handlers](#47-polymorphic-dispatch-with-notification-handlers)
- [4.8. Notification handlers also support open generics](#48-notification-handlers-also-support-open-generics)
- [4.9. Use streaming messages](#49-use-streaming-messages)
- [4.9. Notification publishers](#49-notification-publishers)
- [4.10. Use streaming messages](#410-use-streaming-messages)
- [5. Diagnostics](#5-diagnostics)
- [6. Differences from MediatR](#6-differences-from-mediatr)
- [7. Versioning](#7-versioning)
Expand Down Expand Up @@ -451,8 +452,50 @@ public sealed class GenericNotificationHandler<TNotification> : INotificationHan
}
```

### 4.9. Notification publishers

### 4.9. Use streaming messages
Notification publishers are responsible for dispatching notifications to a collection of handlers.
There are two built in implementations:

* `ForeachAwaitPublisher` - the default, dispatches the notifications to handlers in order 1-by-1
* `TaskWhenAllPublisher` - dispatches notifications in parallel

Both of these try to be efficient by handling a number of special cases (early exit on sync completion, single-handler, array of handlers).
Below we implement a custom one by simply using `Task.WhenAll`.

```csharp
services.AddMediator(options =>
{
options.NotificationPublisherType = typeof(FireAndForgetNotificationPublisher);
});

public sealed class FireAndForgetNotificationPublisher : INotificationPublisher
{
public async ValueTask Publish<TNotification>(
NotificationHandlers<TNotification> handlers,
TNotification notification,
CancellationToken cancellationToken
)
where TNotification : INotification
{
try
{
await Task.WhenAll(handlers.Select(handler => handler.Handle(notification, cancellationToken).AsTask()));
}
catch (Exception ex)
{
// Notifications should be fire-and-forget, we just need to log it!
// This way we don't have to worry about exceptions bubbling up when publishing notifications
Console.Error.WriteLine(ex);

// NOTE: not necessarily saying this is a good idea!
}
}
}
```


### 4.10. Use streaming messages

Since version 1.* of this library there is support for streaming using `IAsyncEnumerable`.

Expand Down
18 changes: 18 additions & 0 deletions benchmarks/Directory.Build.props
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<Project>
<Import Project="..\Directory.Build.props" />

<ItemGroup>
<Compile Include="..\..\common\*.cs">
<Link>%(RecursiveDir)%(Filename)%(Extension)</Link>
</Compile>
</ItemGroup>

<PropertyGroup Condition="'$(Configuration)'!='Debug'">
<TieredCompilation>true</TieredCompilation>
<Optimize>true</Optimize>
</PropertyGroup>

<PropertyGroup>
<DefineConstants Condition="'$(ExtraDefineConstants)' != ''">$(DefineConstants);$(ExtraDefineConstants)</DefineConstants>
</PropertyGroup>
</Project>
8 changes: 0 additions & 8 deletions benchmarks/Mediator.Benchmarks.Large/Config.cs

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -38,28 +38,28 @@ public class RequestBenchmarks
private SomeHandlerClass _handler;
private SomeRequest _request;

[Params(MediatorConfig.Lifetime)]
public ServiceLifetime ServiceLifetime { get; set; } = MediatorConfig.Lifetime;
[Params(Mediator.ServiceLifetime)]
public ServiceLifetime ServiceLifetime { get; set; }

[GlobalSetup]
public void Setup()
{
var services = new ServiceCollection();
services.AddMediator(opts => opts.ServiceLifetime = ServiceLifetime);
services.AddMediator();
services.AddMediatR(opts =>
{
opts.Lifetime = ServiceLifetime;
opts.Lifetime = Mediator.ServiceLifetime;
opts.RegisterServicesFromAssembly(typeof(SomeHandlerClass).Assembly);
});

_serviceProvider = services.BuildServiceProvider();
if (ServiceLifetime == ServiceLifetime.Scoped)
{
#pragma warning disable CS0162 // Unreachable code detected
if (Mediator.ServiceLifetime == ServiceLifetime.Scoped)
{
_serviceScope = _serviceProvider.CreateScope();
#pragma warning restore CS0162 // Unreachable code detected
_serviceProvider = _serviceScope.ServiceProvider;
}
#pragma warning restore CS0162 // Unreachable code detected

_mediator = _serviceProvider.GetRequiredService<IMediator>();
_concreteMediator = _serviceProvider.GetRequiredService<Mediator>();
Expand Down
8 changes: 0 additions & 8 deletions benchmarks/Mediator.Benchmarks/Config.cs

This file was deleted.

38 changes: 38 additions & 0 deletions benchmarks/Mediator.Benchmarks/CustomColumn.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
using BenchmarkDotNet.Columns;
using BenchmarkDotNet.Reports;
using BenchmarkDotNet.Running;

namespace Mediator.Benchmarks;

public sealed class CustomColumn : IColumn
{
private readonly Func<Summary, BenchmarkCase, string> _getTag;

public string Id { get; }
public string ColumnName { get; }

public CustomColumn(string columnName, Func<Summary, BenchmarkCase, string> getTag)
{
_getTag = getTag;
ColumnName = columnName;
Id = nameof(CustomColumn) + "." + ColumnName;
}

public bool IsDefault(Summary summary, BenchmarkCase benchmarkCase) => false;

public string GetValue(Summary summary, BenchmarkCase benchmarkCase) => _getTag(summary, benchmarkCase);

public bool IsAvailable(Summary summary) => true;

public bool AlwaysShow => true;
public ColumnCategory Category => ColumnCategory.Params;
public int PriorityInCategory => 0;
public bool IsNumeric => false;
public UnitType UnitType => UnitType.Dimensionless;
public string Legend => $"Custom '{ColumnName}' tag column";

public string GetValue(Summary summary, BenchmarkCase benchmarkCase, SummaryStyle style) =>
GetValue(summary, benchmarkCase);

public override string ToString() => ColumnName;
}
Loading

0 comments on commit 1858bbc

Please sign in to comment.