-
Notifications
You must be signed in to change notification settings - Fork 803
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add support for RabbitMQ.Client version 7.
RabbitMQ.Client version 7 made major breaking changes - interface renames, all sync methods are removed and only async methods remain. Handle this breaking change by splitting our package into 2, one for each major version. 1. For the current HealthChecks.Rabbitmq package, we put a NuGet version limit on our dependency: [6.8.1,7.0.0). This way people won't be able to update to the 7.0.0 version, which will break their app. 2. We add a new, forked component named HealthChecks.Rabbitmq.v7 which will have a dependency on 7.0.0 and contains updates so the health checks will work with v7. People who explicitly want to use version 7 can opt into using this package. 3. When the next major version of HealthChecks ships, we can "swap" the dependencies around. The HealthChecks.Rabbitmq package will be updated to depend on version 7 of RabbitMQ.Client. If RabbitMQ.Client v6 is still in support, we can create HeatlhChecks.Rabbitmq.v6 which has the dependency limit [6.8.1, 7.0.0) and works with the version 6 of RabbitMQ.Client. HealthChecks.Rabbitmq.v7 will be dead-ended.
- Loading branch information
Showing
8 changed files
with
204 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
24 changes: 24 additions & 0 deletions
24
src/HealthChecks.Rabbitmq.v7/HealthChecks.Rabbitmq.v7.csproj
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
<Project Sdk="Microsoft.NET.Sdk"> | ||
|
||
<PropertyGroup> | ||
<TargetFrameworks>$(DefaultLibraryTargetFrameworks)</TargetFrameworks> | ||
<PackageTags>$(PackageTags);RabbitMQ</PackageTags> | ||
<Description>HealthChecks.RabbitMQ is the health check package for RabbitMQ.Client (version 7+).</Description> | ||
<VersionPrefix>$(HealthCheckRabbitMQ)</VersionPrefix> | ||
<AssemblyName>HealthChecks.Rabbitmq</AssemblyName> | ||
<RootNamespace>HealthChecks.RabbitMQ</RootNamespace> <!--For backward naming compatibility--> | ||
<PackageReadmeFile>README.md</PackageReadmeFile> | ||
</PropertyGroup> | ||
|
||
<ItemGroup> | ||
<PackageReference Include="RabbitMQ.Client" Version="7.0.0" /> | ||
<PackageReference Include="Microsoft.Extensions.Diagnostics.HealthChecks" Version="8.0.0" /> | ||
|
||
<Compile Include="../HealthCheckResultTask.cs" /> | ||
<Compile Include="../HealthChecks.Rabbitmq/DependencyInjection/RabbitMQHealthCheckBuilderExtensions.cs" Link="DependencyInjection/RabbitMQHealthCheckBuilderExtensions.cs" /> | ||
<Compile Include="../HealthChecks.Rabbitmq/RabbitMQHealthCheckOptions.cs" /> | ||
|
||
<None Include="../HealthChecks.Rabbitmq/README.md" Pack="true" PackagePath="\" /> | ||
</ItemGroup> | ||
|
||
</Project> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,113 @@ | ||
using System.Collections.Concurrent; | ||
using Microsoft.Extensions.Diagnostics.HealthChecks; | ||
using RabbitMQ.Client; | ||
|
||
namespace HealthChecks.RabbitMQ; | ||
|
||
/// <summary> | ||
/// A health check for RabbitMQ services. | ||
/// </summary> | ||
public class RabbitMQHealthCheck : IHealthCheck | ||
{ | ||
private static readonly ConcurrentDictionary<RabbitMQHealthCheckOptions, Task<IConnection>> _connections = new(); | ||
|
||
private IConnection? _connection; | ||
private readonly RabbitMQHealthCheckOptions _options; | ||
|
||
public RabbitMQHealthCheck(RabbitMQHealthCheckOptions options) | ||
{ | ||
_options = Guard.ThrowIfNull(options); | ||
_connection = options.Connection; | ||
|
||
if (_connection is null && _options.ConnectionFactory is null && _options.ConnectionUri is null) | ||
{ | ||
throw new ArgumentException("A connection, connection factory, or connection string must be set!", nameof(options)); | ||
} | ||
} | ||
|
||
/// <inheritdoc /> | ||
public async Task<HealthCheckResult> CheckHealthAsync(HealthCheckContext context, CancellationToken cancellationToken = default) | ||
{ | ||
try | ||
{ | ||
var connection = await EnsureConnectionAsync(cancellationToken).ConfigureAwait(false); | ||
await using var model = await connection.CreateChannelAsync(cancellationToken: cancellationToken).ConfigureAwait(false); | ||
|
||
return HealthCheckResult.Healthy(); | ||
} | ||
catch (Exception ex) | ||
{ | ||
return new HealthCheckResult(context.Registration.FailureStatus, exception: ex); | ||
} | ||
} | ||
|
||
private async Task<IConnection> EnsureConnectionAsync(CancellationToken cancellationToken) => | ||
_connection ??= await _connections.GetOrAddAsync(_options, async options => | ||
{ | ||
var factory = options.ConnectionFactory; | ||
|
||
if (factory is null) | ||
{ | ||
Guard.ThrowIfNull(options.ConnectionUri); | ||
factory = new ConnectionFactory | ||
{ | ||
Uri = options.ConnectionUri, | ||
AutomaticRecoveryEnabled = true | ||
}; | ||
|
||
if (options.RequestedConnectionTimeout is not null) | ||
{ | ||
((ConnectionFactory)factory).RequestedConnectionTimeout = options.RequestedConnectionTimeout.Value; | ||
} | ||
|
||
if (options.Ssl is not null) | ||
{ | ||
((ConnectionFactory)factory).Ssl = options.Ssl; | ||
} | ||
} | ||
|
||
return await factory.CreateConnectionAsync(cancellationToken).ConfigureAwait(false); | ||
}).ConfigureAwait(false); | ||
} | ||
|
||
internal static class ConcurrentDictionaryExtensions | ||
{ | ||
/// <summary> | ||
/// Provides an alternative to <see cref="ConcurrentDictionary{TKey, TValue}.GetOrAdd(TKey, Func{TKey, TValue})"/> specifically for asynchronous values. The factory method will only run once. | ||
/// </summary> | ||
public static async Task<TValue> GetOrAddAsync<TKey, TValue>( | ||
this ConcurrentDictionary<TKey, Task<TValue>> dictionary, | ||
TKey key, | ||
Func<TKey, Task<TValue>> valueFactory) where TKey : notnull | ||
{ | ||
while (true) | ||
{ | ||
if (dictionary.TryGetValue(key, out var task)) | ||
{ | ||
return await task.ConfigureAwait(false); | ||
} | ||
|
||
// This is the task that we'll return to all waiters. We'll complete it when the factory is complete | ||
var tcs = new TaskCompletionSource<TValue>(TaskCreationOptions.RunContinuationsAsynchronously); | ||
if (dictionary.TryAdd(key, tcs.Task)) | ||
{ | ||
try | ||
{ | ||
var value = await valueFactory(key).ConfigureAwait(false); | ||
tcs.TrySetResult(value); | ||
return await tcs.Task.ConfigureAwait(false); | ||
} | ||
catch (Exception ex) | ||
{ | ||
// Make sure all waiters see the exception | ||
tcs.SetException(ex); | ||
|
||
// We remove the entry if the factory failed so it's not a permanent failure | ||
// and future gets can retry (this could be a pluggable policy) | ||
dictionary.TryRemove(key, out _); | ||
throw; | ||
} | ||
} | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
4 changes: 4 additions & 0 deletions
4
test/HealthChecks.RabbitMQ.Tests/HealthChecks.RabbitMQ.Tests.csproj
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
16 changes: 16 additions & 0 deletions
16
test/HealthChecks.RabbitMQ.v7.Tests/HealthChecks.RabbitMQ.v7.Tests.csproj
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
<Project Sdk="Microsoft.NET.Sdk"> | ||
|
||
<PropertyGroup> | ||
<AssemblyName>HealthChecks.RabbitMQ.Tests</AssemblyName> | ||
</PropertyGroup> | ||
|
||
<ItemGroup> | ||
<ProjectReference Include="..\..\src\HealthChecks.Rabbitmq.v7\HealthChecks.Rabbitmq.v7.csproj" /> | ||
|
||
<Compile Include="../HealthChecks.RabbitMQ.Tests/DependencyInjection/RegistrationTests.cs" Link="DependencyInjection/RegistrationTests.cs" /> | ||
<Compile Include="../HealthChecks.RabbitMQ.Tests/Functional/RabbitHealthCheckTests.cs" Link="Functional/RabbitHealthCheckTests.cs" /> | ||
|
||
<None Include="../HealthChecks.RabbitMQ.Tests/HealthChecks.Rabbitmq.approved.txt" /> | ||
</ItemGroup> | ||
|
||
</Project> |
28 changes: 28 additions & 0 deletions
28
test/HealthChecks.RabbitMQ.v7.Tests/HealthChecks.Rabbitmq.approved.txt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
namespace HealthChecks.RabbitMQ | ||
{ | ||
public class RabbitMQHealthCheck : Microsoft.Extensions.Diagnostics.HealthChecks.IHealthCheck | ||
{ | ||
public RabbitMQHealthCheck(HealthChecks.RabbitMQ.RabbitMQHealthCheckOptions options) { } | ||
public System.Threading.Tasks.Task<Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckResult> CheckHealthAsync(Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckContext context, System.Threading.CancellationToken cancellationToken = default) { } | ||
} | ||
public class RabbitMQHealthCheckOptions | ||
{ | ||
public RabbitMQHealthCheckOptions() { } | ||
public RabbitMQ.Client.IConnection? Connection { get; set; } | ||
public RabbitMQ.Client.IConnectionFactory? ConnectionFactory { get; set; } | ||
public System.Uri? ConnectionUri { get; set; } | ||
public System.TimeSpan? RequestedConnectionTimeout { get; set; } | ||
public RabbitMQ.Client.SslOption? Ssl { get; set; } | ||
} | ||
} | ||
namespace Microsoft.Extensions.DependencyInjection | ||
{ | ||
public static class RabbitMQHealthCheckBuilderExtensions | ||
{ | ||
public static Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder AddRabbitMQ(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder builder, string? name = null, Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus? failureStatus = default, System.Collections.Generic.IEnumerable<string>? tags = null, System.TimeSpan? timeout = default) { } | ||
public static Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder AddRabbitMQ(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder builder, System.Action<HealthChecks.RabbitMQ.RabbitMQHealthCheckOptions>? setup, string? name = null, Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus? failureStatus = default, System.Collections.Generic.IEnumerable<string>? tags = null, System.TimeSpan? timeout = default) { } | ||
public static Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder AddRabbitMQ(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder builder, System.Action<System.IServiceProvider, HealthChecks.RabbitMQ.RabbitMQHealthCheckOptions>? setup, string? name = null, Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus? failureStatus = default, System.Collections.Generic.IEnumerable<string>? tags = null, System.TimeSpan? timeout = default) { } | ||
public static Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder AddRabbitMQ(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder builder, string rabbitConnectionString, RabbitMQ.Client.SslOption? sslOption = null, string? name = null, Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus? failureStatus = default, System.Collections.Generic.IEnumerable<string>? tags = null, System.TimeSpan? timeout = default) { } | ||
public static Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder AddRabbitMQ(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder builder, System.Uri rabbitConnectionString, RabbitMQ.Client.SslOption? sslOption = null, string? name = null, Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus? failureStatus = default, System.Collections.Generic.IEnumerable<string>? tags = null, System.TimeSpan? timeout = default) { } | ||
} | ||
} |