@@ -109,17 +109,14 @@ public class Client : IClient
109
109
110
110
private Connection connection ;
111
111
112
- private readonly IDictionary < byte , ( Action < ReadOnlyMemory < ulong > > , Action < ( ulong , ResponseCode ) [ ] > ) >
113
- publishers =
114
- new ConcurrentDictionary < byte , ( Action < ReadOnlyMemory < ulong > > , Action < ( ulong , ResponseCode ) [ ] > ) > ( ) ;
115
-
116
112
private readonly ConcurrentDictionary < uint , IValueTaskSource > requests = new ( ) ;
117
113
118
114
private readonly TaskCompletionSource < TuneResponse > tuneReceived =
119
115
new TaskCompletionSource < TuneResponse > ( TaskCreationOptions . RunContinuationsAsynchronously ) ;
120
116
121
- private readonly List < byte > subscriptionIds = new ( ) ;
122
- private readonly List < byte > publisherIds = new ( ) ;
117
+ internal readonly IDictionary < byte , ( Action < ReadOnlyMemory < ulong > > , Action < ( ulong , ResponseCode ) [ ] > ) >
118
+ publishers =
119
+ new ConcurrentDictionary < byte , ( Action < ReadOnlyMemory < ulong > > , Action < ( ulong , ResponseCode ) [ ] > ) > ( ) ;
123
120
124
121
internal readonly IDictionary < byte , ConsumerEvents > consumers =
125
122
new ConcurrentDictionary < byte , ConsumerEvents > ( ) ;
@@ -157,38 +154,11 @@ private Client(ClientParameters parameters, ILogger logger = null)
157
154
IsClosed = false ;
158
155
_logger = logger ?? NullLogger . Instance ;
159
156
ClientId = Guid . NewGuid ( ) . ToString ( ) ;
160
- }
161
-
162
- private byte GetNextSubscriptionId ( )
163
- {
164
- byte result ;
165
- lock ( Obj )
166
- {
167
- result = ConnectionsPool . FindMissingConsecutive ( subscriptionIds ) ;
168
- subscriptionIds . Add ( result ) ;
169
- }
170
-
171
- return result ;
172
- }
173
-
174
- private byte GetNextPublisherId ( )
175
- {
176
- byte result ;
177
- lock ( Obj )
178
- {
179
- result = ConnectionsPool . FindMissingConsecutive ( publisherIds ) ;
180
- publisherIds . Add ( result ) ;
181
- }
182
-
183
- return result ;
184
- }
185
-
186
- internal void RemoveSubscriptionId ( byte id )
187
- {
188
- lock ( Obj )
157
+ AppDomain . CurrentDomain . UnhandledException += ( sender , args ) =>
189
158
{
190
- subscriptionIds . Remove ( id ) ;
191
- }
159
+ _logger . LogError ( args . ExceptionObject as Exception , "Unhandled exception" ) ;
160
+ Parameters . UnhandledExceptionHandler ( args . ExceptionObject as Exception ) ;
161
+ } ;
192
162
}
193
163
194
164
public bool IsClosed
@@ -327,14 +297,23 @@ public ValueTask<bool> Publish<T>(T msg) where T : struct, ICommand
327
297
Action < ReadOnlyMemory < ulong > > confirmCallback ,
328
298
Action < ( ulong , ResponseCode ) [ ] > errorCallback )
329
299
{
330
- var publisherId = GetNextPublisherId ( ) ;
331
- publishers . Add ( publisherId , ( confirmCallback , errorCallback ) ) ;
332
- return ( publisherId , await Request < DeclarePublisherRequest , DeclarePublisherResponse > ( corr =>
333
- new DeclarePublisherRequest ( corr , publisherId , publisherRef , stream ) ) . ConfigureAwait ( false ) ) ;
300
+ await _poolSemaphore . WaitAsync ( ) . ConfigureAwait ( false ) ;
301
+ try
302
+ {
303
+ var publisherId = ConnectionsPool . FindMissingConsecutive ( publishers . Keys . ToList ( ) ) ;
304
+ publishers . Add ( publisherId , ( confirmCallback , errorCallback ) ) ;
305
+ return ( publisherId , await Request < DeclarePublisherRequest , DeclarePublisherResponse > ( corr =>
306
+ new DeclarePublisherRequest ( corr , publisherId , publisherRef , stream ) ) . ConfigureAwait ( false ) ) ;
307
+ }
308
+ finally
309
+ {
310
+ _poolSemaphore . Release ( ) ;
311
+ }
334
312
}
335
313
336
314
public async Task < DeletePublisherResponse > DeletePublisher ( byte publisherId )
337
315
{
316
+ await _poolSemaphore . WaitAsync ( ) . ConfigureAwait ( false ) ;
338
317
try
339
318
{
340
319
var result =
@@ -346,6 +325,7 @@ await Request<DeletePublisherRequest, DeletePublisherResponse>(corr =>
346
325
finally
347
326
{
348
327
publishers . Remove ( publisherId ) ;
328
+ _poolSemaphore . Release ( ) ;
349
329
}
350
330
}
351
331
@@ -366,21 +346,29 @@ await Request<DeletePublisherRequest, DeletePublisherResponse>(corr =>
366
346
Dictionary < string , string > properties , Func < Deliver , Task > deliverHandler ,
367
347
Func < bool , Task < IOffsetType > > consumerUpdateHandler )
368
348
{
369
- var subscriptionId = GetNextSubscriptionId ( ) ;
370
-
371
- consumers . Add ( subscriptionId ,
372
- new ConsumerEvents (
373
- deliverHandler ,
374
- consumerUpdateHandler ) ) ;
349
+ await _poolSemaphore . WaitAsync ( ) . ConfigureAwait ( false ) ;
350
+ try
351
+ {
352
+ var subscriptionId = ConnectionsPool . FindMissingConsecutive ( consumers . Keys . ToList ( ) ) ;
353
+ consumers . Add ( subscriptionId ,
354
+ new ConsumerEvents (
355
+ deliverHandler ,
356
+ consumerUpdateHandler ) ) ;
375
357
376
- return ( subscriptionId ,
377
- await Request < SubscribeRequest , SubscribeResponse > ( corr =>
378
- new SubscribeRequest ( corr , subscriptionId , config . Stream , config . OffsetSpec , initialCredit ,
379
- properties ) ) . ConfigureAwait ( false ) ) ;
358
+ return ( subscriptionId ,
359
+ await Request < SubscribeRequest , SubscribeResponse > ( corr =>
360
+ new SubscribeRequest ( corr , subscriptionId , config . Stream , config . OffsetSpec , initialCredit ,
361
+ properties ) ) . ConfigureAwait ( false ) ) ;
362
+ }
363
+ finally
364
+ {
365
+ _poolSemaphore . Release ( ) ;
366
+ }
380
367
}
381
368
382
369
public async Task < UnsubscribeResponse > Unsubscribe ( byte subscriptionId )
383
370
{
371
+ await _poolSemaphore . WaitAsync ( ) . ConfigureAwait ( false ) ;
384
372
try
385
373
{
386
374
// here we reduce a bit the timeout to avoid waiting too much
@@ -398,6 +386,7 @@ await Request<UnsubscribeRequest, UnsubscribeResponse>(corr =>
398
386
_logger . LogDebug ( "Unsubscribe: {SubscriptionId}" , subscriptionId ) ;
399
387
// remove consumer after RPC returns, this should avoid uncorrelated data being sent
400
388
consumers . Remove ( subscriptionId ) ;
389
+ _poolSemaphore . Release ( ) ;
401
390
}
402
391
}
403
392
@@ -681,6 +670,7 @@ public async Task<CloseResponse> Close(string reason)
681
670
return new CloseResponse ( 0 , ResponseCode . Ok ) ;
682
671
}
683
672
673
+ InternalClose ( ) ;
684
674
try
685
675
{
686
676
var result =
@@ -700,41 +690,54 @@ public async Task<CloseResponse> Close(string reason)
700
690
}
701
691
finally
702
692
{
703
- // even if the close fails we need to close the connection
704
- InternalClose ( ) ;
705
693
connection . Dispose ( ) ;
706
694
}
707
695
708
696
return new CloseResponse ( 0 , ResponseCode . Ok ) ;
709
697
}
710
698
699
+ // _poolSemaphore is introduced here: https://github.com/rabbitmq/rabbitmq-stream-dotnet-client/pull/328
700
+ // the MaybeClose can be called in different threads so we need to protect the pool
701
+ // the pool itself is thread safe but we need to protect the flow to be sure that the
702
+ // connection is released only once
703
+ private readonly SemaphoreSlim _poolSemaphore = new ( 1 , 1 ) ;
704
+
711
705
// Safe close
712
706
// the client can be closed only if HasEntities is false
713
707
// if the client has entities (publishers or consumers) it will be released from the pool
714
708
// Release will decrement the active ids for the connection
715
709
// if the active ids are 0 the connection will be closed
710
+
716
711
internal async Task < CloseResponse > MaybeClose ( string reason , string stream , ConnectionsPool pool )
717
712
{
718
- if ( ! string . IsNullOrEmpty ( ClientId ) )
719
- {
720
- _logger . LogInformation ( "Releasing connection {Connection}" , ClientId ) ;
721
- pool . Release ( ClientId , stream ) ;
722
- }
723
-
724
- if ( ! HasEntities ( ) )
713
+ await _poolSemaphore . WaitAsync ( ) . ConfigureAwait ( false ) ;
714
+ try
725
715
{
726
716
if ( ! string . IsNullOrEmpty ( ClientId ) )
727
717
{
728
- _logger . LogInformation ( "Close connection {Connection}" , ClientId ) ;
729
- // pool.remove(ClientId) is a duplicate call here but it is ok
730
- // the client can be closed in an unexpected way so we need to remove it from the pool
731
- // so you will find pool.remove(ClientId) also to the disconnect event
732
- await Close ( reason ) . ConfigureAwait ( false ) ;
718
+ _logger . LogInformation ( "Releasing connection {Connection}" , ClientId ) ;
719
+ pool . Release ( ClientId , stream ) ;
720
+ }
721
+
722
+ if ( ! HasEntities ( ) )
723
+ {
724
+ if ( ! string . IsNullOrEmpty ( ClientId ) )
725
+ {
726
+ _logger . LogInformation ( "Close connection {Connection}" , ClientId ) ;
727
+ // pool.remove(ClientId) is a duplicate call here but it is ok
728
+ // the client can be closed in an unexpected way so we need to remove it from the pool
729
+ // so you will find pool.remove(ClientId) also to the disconnect event
730
+ await Close ( reason ) . ConfigureAwait ( false ) ;
731
+ }
733
732
}
734
- }
735
733
736
- var result = new CloseResponse ( 0 , ResponseCode . Ok ) ;
737
- return result ;
734
+ var result = new CloseResponse ( 0 , ResponseCode . Ok ) ;
735
+ return result ;
736
+ }
737
+ finally
738
+ {
739
+ _poolSemaphore . Release ( ) ;
740
+ }
738
741
}
739
742
740
743
public string ClientId { get ; init ; }
0 commit comments