profileShare

rasmusjy / splitapp-backend-microservices

Read-only snapshot

No repository description.

main default branch 501 files Expires Sep 13, 2026, 9:06 AM
RabbitMqConnectionProvider.cs 3,274 bytes
1 using Microsoft.Extensions.Logging;
2 using Microsoft.Extensions.Options;
3 using Polly;
4 using Polly.Retry;
5 using RabbitMQ.Client;
6
7 namespace SplitApp.Shared.Messaging.Internal;
8
9 /// <summary>
10 /// Singleton owner of the single AMQP <see cref="IConnection"/> for this process.
11 /// First call to <see cref="GetConnectionAsync"/> performs Polly-backed retries so
12 /// service startup order with RabbitMQ is forgiving.
13 /// </summary>
14 public class RabbitMqConnectionProvider : IAsyncDisposable
15 {
16 private readonly MessagingOptions _options;
17 private readonly ILogger<RabbitMqConnectionProvider> _logger;
18 private readonly SemaphoreSlim _gate = new(1, 1);
19 private IConnection? _connection;
20
21 public RabbitMqConnectionProvider(IOptions<MessagingOptions> options, ILogger<RabbitMqConnectionProvider> logger)
22 {
23 _options = options.Value;
24 _logger = logger;
25 }
26
27 public async Task<IConnection> GetConnectionAsync(CancellationToken cancellationToken = default)
28 {
29 if (_connection is { IsOpen: true }) return _connection;
30
31 await _gate.WaitAsync(cancellationToken);
32 try
33 {
34 if (_connection is { IsOpen: true }) return _connection;
35
36 var factory = new ConnectionFactory
37 {
38 HostName = _options.HostName,
39 Port = _options.Port,
40 UserName = _options.UserName,
41 Password = _options.Password,
42 VirtualHost = _options.VirtualHost,
43 AutomaticRecoveryEnabled = true,
44 TopologyRecoveryEnabled = true,
45 NetworkRecoveryInterval = TimeSpan.FromSeconds(5),
46 };
47
48 var pipeline = new ResiliencePipelineBuilder()
49 .AddRetry(new RetryStrategyOptions
50 {
51 MaxRetryAttempts = _options.InitialConnectRetryAttempts,
52 BackoffType = DelayBackoffType.Exponential,
53 Delay = TimeSpan.FromSeconds(1),
54 MaxDelay = TimeSpan.FromSeconds(30),
55 OnRetry = args =>
56 {
57 _logger.LogWarning(args.Outcome.Exception,
58 "RabbitMQ connect attempt {Attempt} failed; retrying in {Delay}",
59 args.AttemptNumber + 1, args.RetryDelay);
60 return ValueTask.CompletedTask;
61 },
62 })
63 .Build();
64
65 _connection = await pipeline.ExecuteAsync(
66 async ct => await factory.CreateConnectionAsync(ct),
67 cancellationToken);
68
69 _logger.LogInformation("RabbitMQ connection opened to {Host}:{Port}", _options.HostName, _options.Port);
70 return _connection;
71 }
72 finally
73 {
74 _gate.Release();
75 }
76 }
77
78 public async ValueTask DisposeAsync()
79 {
80 if (_connection is not null)
81 {
82 try
83 {
84 await _connection.CloseAsync();
85 await _connection.DisposeAsync();
86 }
87 catch (Exception ex)
88 {
89 _logger.LogWarning(ex, "Error closing RabbitMQ connection");
90 }
91 }
92 _gate.Dispose();
93 }
94 }
95