Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[RabbitMQ] Reusing healthy connection #352

Merged
merged 10 commits into from
Jan 20, 2020
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ public static IHealthChecksBuilder AddRabbitMQ(this IHealthChecksBuilder builder
{
return builder.Add(new HealthCheckRegistration(
name ?? NAME,
sp => new RabbitMQHealthCheck(rabbitMQConnectionString, sslOption),
new RabbitMQHealthCheck(rabbitMQConnectionString, sslOption),
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why?
I

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When passing in a Func to HealthCheckRegistration, that Func gets called every time that a health check is evaluated.
This resulted in a new RabbitMQHealthCheck instance being created for every health check evaluation. This further lead to a new ConnectionFactory and Connection being created for each health check evaluation which eliminated reuse (a RabbitMQHealthCheck will reuse an already provided/created ConnectionFactory/Connection but if a new RabbitMQHealthCheck is created each time then that reuse can't happen since it's a fresh instance each time).

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @rwkarg

True, I try to merge asap!

failureStatus,
tags,
timeout));
Expand Down
156 changes: 84 additions & 72 deletions src/HealthChecks.RabbitMQ/RabbitMQHealthCheck.cs
Original file line number Diff line number Diff line change
@@ -1,72 +1,84 @@
using Microsoft.Extensions.Diagnostics.HealthChecks;
using RabbitMQ.Client;
using System;
using System.Threading;
using System.Threading.Tasks;

namespace HealthChecks.RabbitMQ
{
public class RabbitMQHealthCheck
: IHealthCheck
{
private readonly Lazy<IConnectionFactory> _lazyConnectionFactory;
private readonly IConnection _rmqConnection;

public RabbitMQHealthCheck(string rabbitMqConnectionString, SslOption sslOption = null)
{
if (rabbitMqConnectionString == null) throw new ArgumentNullException(nameof(rabbitMqConnectionString));

_lazyConnectionFactory = new Lazy<IConnectionFactory>(() => new ConnectionFactory()
{
Uri = new Uri(rabbitMqConnectionString),
Ssl = sslOption ?? new SslOption()
});
}

public RabbitMQHealthCheck(IConnection connection)
{
_rmqConnection = connection ?? throw new ArgumentNullException(nameof(connection));
}

public RabbitMQHealthCheck(IConnectionFactory connectionFactory)
{
_lazyConnectionFactory = new Lazy<IConnectionFactory>(() =>
connectionFactory ?? throw new ArgumentNullException(nameof(connectionFactory)));
}

public Task<HealthCheckResult> CheckHealthAsync(HealthCheckContext context, CancellationToken cancellationToken = default)
{
try
{
if (_rmqConnection != null)
{
return TestConnection(_rmqConnection);
}

using (var connection = CreateConnection(_lazyConnectionFactory.Value))
{
return TestConnection(connection);
}
}
catch (Exception ex)
{
return Task.FromResult(
new HealthCheckResult(context.Registration.FailureStatus, exception: ex));
}
}

private static Task<HealthCheckResult> TestConnection(IConnection connection)
{
using (var channel = connection.CreateModel())
{
return Task.FromResult(
HealthCheckResult.Healthy());
}
}

private static IConnection CreateConnection(IConnectionFactory connectionFactory)
{
return connectionFactory.CreateConnection("Health Check Connection");
}
}
}
using Microsoft.Extensions.Diagnostics.HealthChecks;
using RabbitMQ.Client;
using System;
using System.Threading;
using System.Threading.Tasks;

namespace HealthChecks.RabbitMQ
{
public class RabbitMQHealthCheck
: IHealthCheck
{
private readonly IConnectionFactory _connectionFactory;
private IConnection _rmqConnection;

public RabbitMQHealthCheck(string rabbitMqConnectionString, SslOption sslOption = null)
{
var connectionFactory = new ConnectionFactory
{
Uri = new Uri(rabbitMqConnectionString ?? throw new ArgumentNullException(nameof(rabbitMqConnectionString))),
AutomaticRecoveryEnabled = true // Explicitly setting to ensure this is true (in case the default changes)
};
if (sslOption != null)
{
connectionFactory.Ssl = sslOption;
}
_connectionFactory = connectionFactory;
}

public RabbitMQHealthCheck(IConnection connection)
{
_rmqConnection = connection ?? throw new ArgumentNullException(nameof(connection));
}

public RabbitMQHealthCheck(IConnectionFactory connectionFactory)
{
_connectionFactory = connectionFactory ?? throw new ArgumentNullException(nameof(connectionFactory));
}

public Task<HealthCheckResult> CheckHealthAsync(HealthCheckContext context, CancellationToken cancellationToken = default)
{
try
{
// If no factory was provided then we're stuck using the passed in connection
// regardless of the state it may be in. We don't have a way to attempt to
// create a new connection :(
if (_connectionFactory == null)
{
return TestConnection(_rmqConnection);
}

if (_rmqConnection != null && _rmqConnection.IsOpen == false)
{
_rmqConnection.Close(0);
_rmqConnection = null;
}
if (_rmqConnection == null)
{
_rmqConnection = CreateConnection(_connectionFactory);
}

return TestConnection(_rmqConnection);
}
catch (Exception ex)
{
return Task.FromResult(
new HealthCheckResult(context.Registration.FailureStatus, exception: ex));
}
}

private static Task<HealthCheckResult> TestConnection(IConnection connection)
{
using (connection.CreateModel())
{
return Task.FromResult(
HealthCheckResult.Healthy());
}
}

private static IConnection CreateConnection(IConnectionFactory connectionFactory)
{
return connectionFactory.CreateConnection("Health Check Connection");
}
}
}
20 changes: 15 additions & 5 deletions test/UnitTests/DependencyInjection/RabbitMQ/RabbitMQUnitTests.cs
Original file line number Diff line number Diff line change
@@ -1,46 +1,56 @@
using FluentAssertions;
using System;
using System.Collections.Generic;
using FluentAssertions;
using HealthChecks.RabbitMQ;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Diagnostics.HealthChecks;
using Microsoft.Extensions.Options;
using System.Linq;
using System.Threading.Tasks;
using RabbitMQ.Client;
using RabbitMQ.Client.Events;
using Xunit;

namespace UnitTests.HealthChecks.DependencyInjection.RabbitMQ
{
public class rabbitmq_registration_should
{
private string _fakeConnectionString = "amqp://server";
private string _defaultCheckName = "rabbitmq";

[Fact]
public void add_health_check_when_properly_configured()
{
var services = new ServiceCollection();
services.AddHealthChecks()
.AddRabbitMQ(rabbitMQConnectionString: "connectionstring");
.AddRabbitMQ(rabbitMQConnectionString: _fakeConnectionString);

var serviceProvider = services.BuildServiceProvider();
var options = serviceProvider.GetService<IOptions<HealthCheckServiceOptions>>();

var registration = options.Value.Registrations.First();
var check = registration.Factory(serviceProvider);

registration.Name.Should().Be("rabbitmq");
registration.Name.Should().Be(_defaultCheckName);
check.GetType().Should().Be(typeof(RabbitMQHealthCheck));
}

[Fact]
public void add_named_health_check_when_properly_configured()
{
var services = new ServiceCollection();
var customCheckName = "my-"+ _defaultCheckName;

services.AddHealthChecks()
.AddRabbitMQ("connectionstring", name: "my-rabbitmq");
.AddRabbitMQ(_fakeConnectionString, name: customCheckName);

var serviceProvider = services.BuildServiceProvider();
var options = serviceProvider.GetService<IOptions<HealthCheckServiceOptions>>();

var registration = options.Value.Registrations.First();
var check = registration.Factory(serviceProvider);

registration.Name.Should().Be("my-rabbitmq");
registration.Name.Should().Be(customCheckName);
check.GetType().Should().Be(typeof(RabbitMQHealthCheck));
}
}
Expand Down