-
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.
Remove private static cache from RabbitMQHealthCheck (#2343)
* Remove private static cache from RabbitMQHealthCheck RabbitMQHealthCheck is maintaining it's private static cache of instances. It's wrong, as it can cause a memory leak (it won't ever be freed). Moreover, it's can create instances of IConnection. This is wrong, as it can lead into a situation when we have multiple instances of IConnection that are connected to the same server: one used by the app and another created and used by the health check. And we should have only one. We should do the same as in #2040, #2116 and #2096 I left RabbitMQ.v6 as-is to limit the breaking change. Only when an existing user moves from RabbitMQ.Client v6 to v7 will they need to update their health check (along with other breaking changes in RabbitMQ.Client). * Add test when no IConnection is registered in DI.
- Loading branch information
Showing
14 changed files
with
805 additions
and
436 deletions.
There are no files selected for viewing
207 changes: 207 additions & 0 deletions
207
src/HealthChecks.Rabbitmq.v6/DependencyInjection/RabbitMQHealthCheckBuilderExtensions.cs
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,207 @@ | ||
using HealthChecks.RabbitMQ; | ||
using Microsoft.Extensions.Diagnostics.HealthChecks; | ||
using RabbitMQ.Client; | ||
|
||
namespace Microsoft.Extensions.DependencyInjection; | ||
|
||
/// <summary> | ||
/// Extension methods to configure <see cref="RabbitMQHealthCheck"/>. | ||
/// </summary> | ||
public static class RabbitMQHealthCheckBuilderExtensions | ||
{ | ||
private const string NAME = "rabbitmq"; | ||
|
||
/// <summary> | ||
/// Add a health check for RabbitMQ services using connection string (amqp uri). | ||
/// </summary> | ||
/// <param name="builder">The <see cref="IHealthChecksBuilder"/>.</param> | ||
/// <param name="rabbitConnectionString">The RabbitMQ connection string to be used.</param> | ||
/// <param name="sslOption">The RabbitMQ ssl options. Optional. If <c>null</c>, the ssl option will counted as disabled and not used.</param> | ||
/// <param name="name">The health check name. Optional. If <c>null</c> the type name 'rabbitmq' will be used for the name.</param> | ||
/// <param name="failureStatus"> | ||
/// The <see cref="HealthStatus"/> that should be reported when the health check fails. Optional. If <c>null</c> then | ||
/// the default status of <see cref="HealthStatus.Unhealthy"/> will be reported. | ||
/// </param> | ||
/// <param name="tags">A list of tags that can be used to filter sets of health checks. Optional.</param> | ||
/// <param name="timeout">An optional <see cref="TimeSpan"/> representing the timeout of the check.</param> | ||
/// <returns>The specified <paramref name="builder"/>.</returns> | ||
public static IHealthChecksBuilder AddRabbitMQ( | ||
this IHealthChecksBuilder builder, | ||
string rabbitConnectionString, | ||
SslOption? sslOption = default, | ||
string? name = default, | ||
HealthStatus? failureStatus = default, | ||
IEnumerable<string>? tags = default, | ||
TimeSpan? timeout = default) | ||
{ | ||
return builder.AddRabbitMQ(new Uri(rabbitConnectionString), sslOption, name, failureStatus, tags, timeout); | ||
} | ||
|
||
/// <summary> | ||
/// Add a health check for RabbitMQ services using connection string (amqp uri). | ||
/// </summary> | ||
/// <param name="builder">The <see cref="IHealthChecksBuilder"/>.</param> | ||
/// <param name="rabbitConnectionString">The RabbitMQ connection string to be used.</param> | ||
/// <param name="sslOption">The RabbitMQ ssl options. Optional. If <c>null</c>, the ssl option will counted as disabled and not used.</param> | ||
/// <param name="name">The health check name. Optional. If <c>null</c> the type name 'rabbitmq' will be used for the name.</param> | ||
/// <param name="failureStatus"> | ||
/// The <see cref="HealthStatus"/> that should be reported when the health check fails. Optional. If <c>null</c> then | ||
/// the default status of <see cref="HealthStatus.Unhealthy"/> will be reported. | ||
/// </param> | ||
/// <param name="tags">A list of tags that can be used to filter sets of health checks. Optional.</param> | ||
/// <param name="timeout">An optional <see cref="TimeSpan"/> representing the timeout of the check.</param> | ||
/// <returns>The specified <paramref name="builder"/>.</returns> | ||
public static IHealthChecksBuilder AddRabbitMQ( | ||
this IHealthChecksBuilder builder, | ||
Uri rabbitConnectionString, | ||
SslOption? sslOption = default, | ||
string? name = default, | ||
HealthStatus? failureStatus = default, | ||
IEnumerable<string>? tags = default, | ||
TimeSpan? timeout = default) | ||
{ | ||
var options = new RabbitMQHealthCheckOptions | ||
{ | ||
ConnectionUri = rabbitConnectionString, | ||
Ssl = sslOption | ||
}; | ||
|
||
return builder.Add(new HealthCheckRegistration( | ||
name ?? NAME, | ||
new RabbitMQHealthCheck(options), | ||
failureStatus, | ||
tags, | ||
timeout)); | ||
} | ||
|
||
/// <summary> | ||
/// Add a health check for RabbitMQ services using <see cref="IConnection"/> from service provider | ||
/// or <see cref="IConnectionFactory"/> from service provider if none is found. At least one must be configured. | ||
/// </summary> | ||
/// <remarks> | ||
/// This method shouldn't be called more than once. | ||
/// Each subsequent call will create a new connection, which overrides the previous ones. | ||
/// </remarks> | ||
/// <param name="builder">The <see cref="IHealthChecksBuilder"/>.</param> | ||
/// <param name="name">The health check name. Optional. If <c>null</c> the type name 'rabbitmq' will be used for the name.</param> | ||
/// <param name="failureStatus"> | ||
/// The <see cref="HealthStatus"/> that should be reported when the health check fails. Optional. If <c>null</c> then | ||
/// the default status of <see cref="HealthStatus.Unhealthy"/> will be reported. | ||
/// </param> | ||
/// <param name="tags">A list of tags that can be used to filter sets of health checks. Optional.</param> | ||
/// <param name="timeout">An optional <see cref="TimeSpan"/> representing the timeout of the check.</param> | ||
/// <returns>The specified <paramref name="builder"/>.</returns> | ||
public static IHealthChecksBuilder AddRabbitMQ( | ||
this IHealthChecksBuilder builder, | ||
string? name = default, | ||
HealthStatus? failureStatus = default, | ||
IEnumerable<string>? tags = default, | ||
TimeSpan? timeout = default) | ||
{ | ||
builder.Services.AddSingleton(sp => | ||
{ | ||
var connection = sp.GetService<IConnection>(); | ||
var connectionFactory = sp.GetService<IConnectionFactory>(); | ||
|
||
if (connection != null) | ||
{ | ||
return new RabbitMQHealthCheck(new RabbitMQHealthCheckOptions { Connection = connection }); | ||
} | ||
else if (connectionFactory != null) | ||
{ | ||
return new RabbitMQHealthCheck(new RabbitMQHealthCheckOptions { ConnectionFactory = connectionFactory }); | ||
} | ||
else | ||
{ | ||
throw new ArgumentException($"Either an IConnection or IConnectionFactory must be registered with the service provider"); | ||
} | ||
}); | ||
|
||
return builder.Add(new HealthCheckRegistration( | ||
name ?? NAME, | ||
sp => sp.GetRequiredService<RabbitMQHealthCheck>(), | ||
failureStatus, | ||
tags, | ||
timeout)); | ||
} | ||
|
||
/// <summary> | ||
/// Add a health check for RabbitMQ services. | ||
/// </summary> | ||
/// <remarks> | ||
/// <paramref name="setup"/> will be called each time the healthcheck route is requested. However | ||
/// the created <see cref="IConnection"/> will be reused. | ||
/// </remarks> | ||
/// <param name="builder">The <see cref="IHealthChecksBuilder"/>.</param> | ||
/// <param name="setup">The action to configure the RabbitMQ setup.</param> | ||
/// <param name="name">The health check name. Optional. If <c>null</c> the type name 'rabbitmq' will be used for the name.</param> | ||
/// <param name="failureStatus"> | ||
/// The <see cref="HealthStatus"/> that should be reported when the health check fails. Optional. If <c>null</c> then | ||
/// the default status of <see cref="HealthStatus.Unhealthy"/> will be reported. | ||
/// </param> | ||
/// <param name="tags">A list of tags that can be used to filter sets of health checks. Optional.</param> | ||
/// <param name="timeout">An optional <see cref="TimeSpan"/> representing the timeout of the check.</param> | ||
/// <returns>The specified <paramref name="builder"/>.</returns> | ||
public static IHealthChecksBuilder AddRabbitMQ( | ||
this IHealthChecksBuilder builder, | ||
Action<RabbitMQHealthCheckOptions>? setup, | ||
string? name = default, | ||
HealthStatus? failureStatus = default, | ||
IEnumerable<string>? tags = default, | ||
TimeSpan? timeout = default) | ||
{ | ||
var options = new RabbitMQHealthCheckOptions(); | ||
setup?.Invoke(options); | ||
|
||
return builder.Add(new HealthCheckRegistration( | ||
name ?? NAME, | ||
new RabbitMQHealthCheck(options), | ||
failureStatus, | ||
tags, | ||
timeout)); | ||
} | ||
|
||
/// <summary> | ||
/// Add a health check for RabbitMQ services. | ||
/// </summary> | ||
/// <remarks> | ||
/// <paramref name="setup"/> will be called the first time the healthcheck route is requested. However | ||
/// the created <see cref="IConnection"/> will be reused. | ||
/// </remarks> | ||
/// <param name="builder">The <see cref="IHealthChecksBuilder"/>.</param> | ||
/// <param name="setup">The action to configure the RabbitMQ setup with <see cref="IServiceProvider"/>.</param> | ||
/// <param name="name">The health check name. Optional. If <c>null</c> the type name 'rabbitmq' will be used for the name.</param> | ||
/// <param name="failureStatus"> | ||
/// The <see cref="HealthStatus"/> that should be reported when the health check fails. Optional. If <c>null</c> then | ||
/// the default status of <see cref="HealthStatus.Unhealthy"/> will be reported. | ||
/// </param> | ||
/// <param name="tags">A list of tags that can be used to filter sets of health checks. Optional.</param> | ||
/// <param name="timeout">An optional <see cref="TimeSpan"/> representing the timeout of the check.</param> | ||
/// <returns>The specified <paramref name="builder"/>.</returns> | ||
public static IHealthChecksBuilder AddRabbitMQ( | ||
this IHealthChecksBuilder builder, | ||
Action<IServiceProvider, RabbitMQHealthCheckOptions>? setup, | ||
string? name = default, | ||
HealthStatus? failureStatus = default, | ||
IEnumerable<string>? tags = default, | ||
TimeSpan? timeout = default) | ||
{ | ||
var options = new RabbitMQHealthCheckOptions(); | ||
|
||
return builder.Add(new HealthCheckRegistration( | ||
name ?? NAME, | ||
sp => | ||
{ | ||
if (!options.AlreadyConfiguredByHealthCheckRegistrationCall) | ||
{ | ||
setup?.Invoke(sp, options); | ||
options.AlreadyConfiguredByHealthCheckRegistrationCall = true; | ||
} | ||
|
||
return new RabbitMQHealthCheck(options); | ||
}, | ||
failureStatus, | ||
tags, | ||
timeout)); | ||
} | ||
} |
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,91 @@ | ||
# RabbitMQ Health Check | ||
|
||
This health check verifies the ability to communicate with a RabbitMQ server | ||
|
||
## Example Usage | ||
|
||
With all of the following examples, you can additionally add the following parameters: | ||
|
||
- `name`: The health check name. Default if not specified is `rabbitmq`. | ||
- `failureStatus`: The `HealthStatus` that should be reported when the health check fails. Default is `HealthStatus.Unhealthy`. | ||
- `tags`: A list of tags that can be used to filter sets of health checks. | ||
- `timeout`: A `System.TimeSpan` representing the timeout of the check. | ||
|
||
### Basic | ||
|
||
This will create a new `IConnection` and reuse on every request to get the health check result. Use | ||
the extension method where you provide the `Uri` to connect with. You can optionally set the `SslOption` if needed. | ||
IConnection created with this option use UseBackgroundThreadsForIO by default in order to gracefully shutdown on non reference IConnection by ServiceCollection. | ||
|
||
```csharp | ||
public void ConfigureServices(IServiceCollection services) | ||
{ | ||
services | ||
.AddHealthChecks() | ||
.AddRabbitMQ(rabbitConnectionString: "amqps://user:pass@host1/vhost") | ||
.AddRabbitMQ(rabbitConnectionString: "amqps://user:pass@host2/vhost"); | ||
} | ||
``` | ||
|
||
### Dependency Injected `IConnection` | ||
|
||
As per [RabbitMQ docs](https://www.rabbitmq.com/connections.html) and its suggestions on | ||
[high connectivity churn](https://www.rabbitmq.com/networking.html#dealing-with-high-connection-churn), connections are meant to be long lived. | ||
Ideally, this should be configured as a singleton. | ||
|
||
If you are sharing a single connection for every time a health check is requested, | ||
you must ensure automatic recovery is enable so that the connection can be re-established if lost. | ||
|
||
```csharp | ||
public void ConfigureServices(IServiceCollection services) | ||
{ | ||
services | ||
.AddSingleton<IConnection>(sp => | ||
{ | ||
var factory = new ConnectionFactory | ||
{ | ||
Uri = new Uri("amqps://user:pass@host/vhost"), | ||
AutomaticRecoveryEnabled = true | ||
}; | ||
return factory.CreateConnection(); | ||
}) | ||
.AddHealthChecks() | ||
.AddRabbitMQ(); | ||
} | ||
``` | ||
|
||
Alternatively, you can specify the connection to use with a factory function given the `IServiceProvider`. | ||
|
||
```csharp | ||
public void ConfigureServices(IServiceCollection services) | ||
{ | ||
services | ||
.AddHealthChecks() | ||
.AddRabbitMQ(sp => | ||
{ | ||
var factory = new ConnectionFactory | ||
{ | ||
Uri = new Uri("amqps://user:pass@host/vhost"), | ||
AutomaticRecoveryEnabled = true | ||
}; | ||
return factory.CreateConnection(); | ||
}); | ||
} | ||
``` | ||
|
||
Or you register IConnectionFactory and then the healthcheck will create a single connection for that one. | ||
|
||
```csharp | ||
public void ConfigureServices(IServiceCollection services) | ||
{ | ||
services | ||
.AddSingleton<IConnectionFactory>(sp => | ||
new ConnectionFactory | ||
{ | ||
Uri = new Uri("amqps://user:pass@host/vhost"), | ||
AutomaticRecoveryEnabled = true | ||
}) | ||
.AddHealthChecks() | ||
.AddRabbitMQ(); | ||
} | ||
``` |
File renamed without changes.
Oops, something went wrong.