Skip to content

Commit 82a3fcd

Browse files
committed
* Renaming methods that return Task or Task<T> to end with Async suffic
* Re-ordering methods in `AmqpConnection` so that they are in public, internal, private order * Enable semaphores in `AmqpConnection` and add `IDisposable`
1 parent 49a1161 commit 82a3fcd

File tree

4 files changed

+145
-127
lines changed

4 files changed

+145
-127
lines changed

RabbitMQ.AMQP.Client/Impl/AbstractLifeCycle.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ internal void ChangeStatus(State newState, Error? error)
6161
OnNewStatus(newState, error);
6262
}
6363

64-
internal async Task Reconnect()
64+
internal async Task ReconnectAsync()
6565
{
6666
try
6767
{
@@ -84,7 +84,7 @@ internal async Task Reconnect()
8484
await Task.Delay(delay).ConfigureAwait(false);
8585
if (_backOffDelayPolicy.IsActive())
8686
{
87-
await Reconnect().ConfigureAwait(false);
87+
await ReconnectAsync().ConfigureAwait(false);
8888
}
8989
}
9090
}

RabbitMQ.AMQP.Client/Impl/AmqpConnection.cs

Lines changed: 139 additions & 122 deletions
Original file line numberDiff line numberDiff line change
@@ -11,81 +11,30 @@ namespace RabbitMQ.AMQP.Client.Impl;
1111
/// AmqpConnection is the concrete implementation of <see cref="IConnection"/>
1212
/// It is a wrapper around the AMQP.Net Lite <see cref="Connection"/> class
1313
/// </summary>
14-
public class AmqpConnection : AbstractLifeCycle, IConnection
14+
public class AmqpConnection : AbstractLifeCycle, IConnection, IDisposable
1515
{
1616
private const string ConnectionNotRecoveredCode = "CONNECTION_NOT_RECOVERED";
1717
private const string ConnectionNotRecoveredMessage = "Connection not recovered";
18-
private readonly SemaphoreSlim _semaphoreClose = new(1, 1); // TODO this needs to be Disposed
18+
19+
private readonly SemaphoreSlim _semaphoreClose = new(1, 1);
20+
private readonly SemaphoreSlim _semaphoreOpen = new(1, 1);
1921

2022
// The native AMQP.Net Lite connection
2123
private Connection? _nativeConnection;
2224

2325
private readonly AmqpManagement _management;
2426
private readonly RecordingTopologyListener _recordingTopologyListener = new();
2527

26-
private void ChangeEntitiesStatus(State state, Error? error)
27-
{
28-
ChangePublishersStatus(state, error);
29-
ChangeConsumersStatus(state, error);
30-
_management.ChangeStatus(state, error);
31-
}
32-
33-
private void ChangePublishersStatus(State state, Error? error)
34-
{
35-
foreach (IPublisher publisher1 in Publishers.Values)
36-
{
37-
var publisher = (AmqpPublisher)publisher1;
38-
publisher.ChangeStatus(state, error);
39-
}
40-
}
41-
42-
private void ChangeConsumersStatus(State state, Error? error)
43-
{
44-
foreach (IConsumer consumer1 in Consumers.Values)
45-
{
46-
var consumer = (AmqpConsumer)consumer1;
47-
consumer.ChangeStatus(state, error);
48-
}
49-
}
50-
51-
private async Task ReconnectEntities()
52-
{
53-
await ReconnectPublishers().ConfigureAwait(false);
54-
await ReconnectConsumers().ConfigureAwait(false);
55-
}
56-
57-
private async Task ReconnectPublishers()
58-
{
59-
foreach (IPublisher publisher1 in Publishers.Values)
60-
{
61-
var publisher = (AmqpPublisher)publisher1;
62-
await publisher.Reconnect().ConfigureAwait(false);
63-
}
64-
}
65-
66-
private async Task ReconnectConsumers()
67-
{
68-
foreach (IConsumer consumer1 in Consumers.Values)
69-
{
70-
var consumer = (AmqpConsumer)consumer1;
71-
await consumer.Reconnect().ConfigureAwait(false);
72-
}
73-
}
74-
7528
private readonly IConnectionSettings _connectionSettings;
7629
internal readonly AmqpSessionManagement _nativePubSubSessions;
7730

78-
// TODO: Implement the semaphore to avoid multiple connections
79-
// private readonly SemaphoreSlim _semaphore = new(1, 1);
80-
8131
/// <summary>
8232
/// Publishers contains all the publishers created by the connection.
8333
/// Each connection can have multiple publishers.
8434
/// They key is the publisher Id ( a Guid)
8535
/// See <see cref="AmqpPublisher"/>
8636
/// </summary>
8737
internal ConcurrentDictionary<string, IPublisher> Publishers { get; } = new();
88-
8938
internal ConcurrentDictionary<string, IConsumer> Consumers { get; } = new();
9039

9140
public ReadOnlyCollection<IPublisher> GetPublishers()
@@ -116,6 +65,87 @@ await connection.OpenAsync()
11665
return connection;
11766
}
11867

68+
public IManagement Management()
69+
{
70+
return _management;
71+
}
72+
73+
public IConsumerBuilder ConsumerBuilder()
74+
{
75+
return new AmqpConsumerBuilder(this);
76+
}
77+
78+
public override async Task OpenAsync()
79+
{
80+
await OpenConnectionAsync()
81+
.ConfigureAwait(false);
82+
await base.OpenAsync()
83+
.ConfigureAwait(false);
84+
}
85+
86+
public IPublisherBuilder PublisherBuilder()
87+
{
88+
ThrowIfClosed();
89+
var publisherBuilder = new AmqpPublisherBuilder(this);
90+
return publisherBuilder;
91+
}
92+
93+
public override async Task CloseAsync()
94+
{
95+
await _semaphoreClose.WaitAsync()
96+
.ConfigureAwait(false);
97+
try
98+
{
99+
await CloseAllPublishers().ConfigureAwait(false);
100+
await CloseAllConsumers().ConfigureAwait(false);
101+
102+
_recordingTopologyListener.Clear();
103+
_nativePubSubSessions.ClearSessions();
104+
105+
if (State == State.Closed)
106+
{
107+
return;
108+
}
109+
110+
OnNewStatus(State.Closing, null);
111+
112+
await _management.CloseAsync()
113+
.ConfigureAwait(false);
114+
115+
if (_nativeConnection is { IsClosed: false })
116+
{
117+
await _nativeConnection.CloseAsync()
118+
.ConfigureAwait(false);
119+
}
120+
}
121+
finally
122+
{
123+
_semaphoreClose.Release();
124+
}
125+
126+
await ConnectionCloseTaskCompletionSource.Task.WaitAsync(TimeSpan.FromSeconds(10))
127+
.ConfigureAwait(false);
128+
129+
OnNewStatus(State.Closed, null);
130+
}
131+
132+
public void Dispose()
133+
{
134+
// TODO probably more should happen in this method
135+
_semaphoreOpen.Dispose();
136+
_semaphoreClose.Dispose();
137+
}
138+
139+
public override string ToString()
140+
{
141+
string info = $"AmqpConnection{{ConnectionSettings='{_connectionSettings}', Status='{State.ToString()}'}}";
142+
return info;
143+
}
144+
145+
internal Connection? NativeConnection()
146+
{
147+
return _nativeConnection;
148+
}
119149

120150
/// <summary>
121151
/// Closes all the publishers. It is called when the connection is closed.
@@ -150,28 +180,11 @@ private AmqpConnection(IConnectionSettings connectionSettings)
150180
new AmqpManagement(new AmqpManagementParameters(this).TopologyListener(_recordingTopologyListener));
151181
}
152182

153-
public IManagement Management()
154-
{
155-
return _management;
156-
}
157-
158-
public IConsumerBuilder ConsumerBuilder()
183+
// TODO cancellation token
184+
private async Task OpenConnectionAsync()
159185
{
160-
return new AmqpConsumerBuilder(this);
161-
}
162-
163-
public override async Task OpenAsync()
164-
{
165-
await EnsureConnection()
186+
await _semaphoreOpen.WaitAsync()
166187
.ConfigureAwait(false);
167-
await base.OpenAsync()
168-
.ConfigureAwait(false);
169-
}
170-
171-
private async Task EnsureConnection()
172-
{
173-
// TODO: do this!
174-
// await _semaphore.WaitAsync();
175188
try
176189
{
177190
if (_nativeConnection is { IsClosed: false })
@@ -252,27 +265,34 @@ await _management.OpenAsync()
252265

253266
_nativeConnection.Closed += MaybeRecoverConnection();
254267
}
255-
256268
catch (AmqpException e)
257269
{
258270
Trace.WriteLine(TraceLevel.Error, $"Error trying to connect. Info: {ToString()}, error: {e}");
259271
throw new ConnectionException($"Error trying to connect. Info: {ToString()}, error: {e}");
260272
}
273+
finally
274+
{
275+
_semaphoreOpen.Release();
276+
}
261277
}
262278

263279
/// <summary>
264280
/// Event handler for the connection closed event.
265281
/// In case the error is null means that the connection is closed by the user.
266282
/// The recover mechanism is activated only if the error is not null.
267283
/// The connection maybe recovered if the recovery configuration is active.
284+
///
285+
/// TODO this method could be improved.
286+
/// MaybeRecoverConnection should set a connection state to RECOVERING
287+
/// and then kick off a task dedicated to recovery
268288
/// </summary>
269289
/// <returns></returns>
270290
private ClosedCallback MaybeRecoverConnection()
271291
{
272292
return async (sender, error) =>
273293
{
274-
await _semaphoreClose.WaitAsync().ConfigureAwait(false);
275-
294+
await _semaphoreClose.WaitAsync()
295+
.ConfigureAwait(false);
276296
try
277297
{
278298
// close all the sessions, if the connection is closed the sessions are not valid anymore
@@ -299,7 +319,6 @@ private ClosedCallback MaybeRecoverConnection()
299319
OnNewStatus(State.Reconnecting, Utils.ConvertError(error));
300320
ChangeEntitiesStatus(State.Reconnecting, Utils.ConvertError(error));
301321

302-
303322
await Task.Run(async () =>
304323
{
305324
bool connected = false;
@@ -328,7 +347,7 @@ await Task.Run(async () =>
328347
await Task.Delay(TimeSpan.FromMilliseconds(next))
329348
.ConfigureAwait(false);
330349

331-
await EnsureConnection()
350+
await OpenConnectionAsync()
332351
.ConfigureAwait(false);
333352
connected = true;
334353
}
@@ -370,7 +389,7 @@ await _recordingTopologyListener.Accept(visitor)
370389

371390
try
372391
{
373-
await ReconnectEntities().ConfigureAwait(false);
392+
await ReconnectEntitiesAsync().ConfigureAwait(false);
374393
}
375394
catch (Exception e)
376395
{
@@ -394,70 +413,68 @@ await _recordingTopologyListener.Accept(visitor)
394413
};
395414
}
396415

397-
internal Connection? NativeConnection()
416+
private void ChangeEntitiesStatus(State state, Error? error)
398417
{
399-
return _nativeConnection;
418+
ChangePublishersStatus(state, error);
419+
ChangeConsumersStatus(state, error);
420+
_management.ChangeStatus(state, error);
400421
}
401422

402-
public IPublisherBuilder PublisherBuilder()
423+
private void ChangePublishersStatus(State state, Error? error)
403424
{
404-
ThrowIfClosed();
405-
var publisherBuilder = new AmqpPublisherBuilder(this);
406-
return publisherBuilder;
425+
foreach (IPublisher publisher1 in Publishers.Values)
426+
{
427+
var publisher = (AmqpPublisher)publisher1;
428+
publisher.ChangeStatus(state, error);
429+
}
407430
}
408431

409-
public override async Task CloseAsync()
432+
private void ChangeConsumersStatus(State state, Error? error)
410433
{
411-
await _semaphoreClose.WaitAsync()
412-
.ConfigureAwait(false);
413-
try
414-
{
415-
await CloseAllPublishers().ConfigureAwait(false);
416-
await CloseAllConsumers().ConfigureAwait(false);
417-
418-
_recordingTopologyListener.Clear();
419-
_nativePubSubSessions.ClearSessions();
420-
421-
if (State == State.Closed)
422-
{
423-
return;
424-
}
425-
426-
OnNewStatus(State.Closing, null);
427-
428-
await _management.CloseAsync()
429-
.ConfigureAwait(false);
430-
431-
if (_nativeConnection is { IsClosed: false })
432-
{
433-
await _nativeConnection.CloseAsync()
434-
.ConfigureAwait(false);
435-
}
436-
}
437-
finally
434+
foreach (IConsumer consumer1 in Consumers.Values)
438435
{
439-
_semaphoreClose.Release();
436+
var consumer = (AmqpConsumer)consumer1;
437+
consumer.ChangeStatus(state, error);
440438
}
439+
}
441440

442-
await ConnectionCloseTaskCompletionSource.Task.WaitAsync(TimeSpan.FromSeconds(10))
441+
private async Task ReconnectEntitiesAsync()
442+
{
443+
await ReconnectPublishersAsync()
443444
.ConfigureAwait(false);
445+
await ReconnectConsumersAsync()
446+
.ConfigureAwait(false);
447+
}
444448

445-
OnNewStatus(State.Closed, null);
449+
private async Task ReconnectPublishersAsync()
450+
{
451+
// TODO this could be done in parallel
452+
foreach (IPublisher publisher1 in Publishers.Values)
453+
{
454+
var publisher = (AmqpPublisher)publisher1;
455+
await publisher.ReconnectAsync()
456+
.ConfigureAwait(false);
457+
}
446458
}
447459

448-
public override string ToString()
460+
private async Task ReconnectConsumersAsync()
449461
{
450-
string info = $"AmqpConnection{{ConnectionSettings='{_connectionSettings}', Status='{State.ToString()}'}}";
451-
return info;
462+
// TODO this could be done in parallel
463+
foreach (IConsumer consumer1 in Consumers.Values)
464+
{
465+
var consumer = (AmqpConsumer)consumer1;
466+
await consumer.ReconnectAsync().ConfigureAwait(false);
467+
}
452468
}
453469
}
454470

455471
internal class Visitor(AmqpManagement management) : IVisitor
456472
{
457473
private AmqpManagement Management { get; } = management;
458474

459-
public async Task VisitQueues(IEnumerable<QueueSpec> queueSpec)
475+
public async Task VisitQueuesAsync(IEnumerable<QueueSpec> queueSpec)
460476
{
477+
// TODO this could be done in parallel
461478
foreach (QueueSpec spec in queueSpec)
462479
{
463480
Trace.WriteLine(TraceLevel.Information, $"Recovering queue {spec.Name}");

0 commit comments

Comments
 (0)