@@ -53,7 +53,9 @@ public record ClientParameters
53
53
public string VirtualHost { get ; set ; } = "/" ;
54
54
public EndPoint Endpoint { get ; set ; } = new IPEndPoint ( IPAddress . Loopback , 5552 ) ;
55
55
56
- public Action < MetaDataUpdate > MetadataHandler { get ; set ; } = _ => { } ;
56
+ public delegate void MetadataUpdateHandler ( MetaDataUpdate update ) ;
57
+
58
+ public event MetadataUpdateHandler OnMetadataUpdate ;
57
59
public Action < Exception > UnhandledExceptionHandler { get ; set ; } = _ => { } ;
58
60
public TimeSpan Heartbeat { get ; set ; } = TimeSpan . FromMinutes ( 1 ) ;
59
61
@@ -71,6 +73,11 @@ public string ClientProvidedName
71
73
public AddressResolver AddressResolver { get ; set ; } = null ;
72
74
73
75
public AuthMechanism AuthMechanism { get ; set ; } = AuthMechanism . Plain ;
76
+
77
+ internal void FireMetadataUpdate ( MetaDataUpdate metaDataUpdate )
78
+ {
79
+ OnMetadataUpdate ? . Invoke ( metaDataUpdate ) ;
80
+ }
74
81
}
75
82
76
83
internal readonly struct OutgoingMsg : ICommand
@@ -213,7 +220,8 @@ await client
213
220
. ConfigureAwait ( false ) ;
214
221
logger ? . LogDebug ( "Sasl mechanism: {Mechanisms}" , saslHandshakeResponse . Mechanisms ) ;
215
222
216
- var isValid = saslHandshakeResponse . Mechanisms . Contains ( parameters . AuthMechanism . ToString ( ) . ToUpperInvariant ( ) ,
223
+ var isValid = saslHandshakeResponse . Mechanisms . Contains (
224
+ parameters . AuthMechanism . ToString ( ) . ToUpperInvariant ( ) ,
217
225
StringComparer . OrdinalIgnoreCase ) ;
218
226
if ( ! isValid )
219
227
{
@@ -225,7 +233,8 @@ await client
225
233
var authResponse =
226
234
await client
227
235
. Request < SaslAuthenticateRequest , SaslAuthenticateResponse > ( corr =>
228
- new SaslAuthenticateRequest ( corr , parameters . AuthMechanism . ToString ( ) . ToUpperInvariant ( ) , saslData ) )
236
+ new SaslAuthenticateRequest ( corr , parameters . AuthMechanism . ToString ( ) . ToUpperInvariant ( ) ,
237
+ saslData ) )
229
238
. ConfigureAwait ( false ) ;
230
239
ClientExceptions . MaybeThrowException ( authResponse . ResponseCode , parameters . UserName ) ;
231
240
@@ -322,22 +331,28 @@ public ValueTask<bool> Publish<T>(T msg) where T : struct, ICommand
322
331
return ( publisherId , response ) ;
323
332
}
324
333
325
- public async Task < DeletePublisherResponse > DeletePublisher ( byte publisherId )
334
+ public async Task < DeletePublisherResponse > DeletePublisher ( byte publisherId ,
335
+ bool ignoreIfAlreadyRemoved = false )
326
336
{
327
337
await _poolSemaphore . WaitAsync ( ) . ConfigureAwait ( false ) ;
328
338
try
329
339
{
330
- var result =
331
- await Request < DeletePublisherRequest , DeletePublisherResponse > ( corr =>
332
- new DeletePublisherRequest ( corr , publisherId ) ) . ConfigureAwait ( false ) ;
340
+ if ( ! ignoreIfAlreadyRemoved )
341
+ {
342
+ var result =
343
+ await Request < DeletePublisherRequest , DeletePublisherResponse > ( corr =>
344
+ new DeletePublisherRequest ( corr , publisherId ) ) . ConfigureAwait ( false ) ;
333
345
334
- return result ;
346
+ return result ;
347
+ }
335
348
}
336
349
finally
337
350
{
338
351
publishers . Remove ( publisherId ) ;
339
352
_poolSemaphore . Release ( ) ;
340
353
}
354
+
355
+ return new DeletePublisherResponse ( ) ;
341
356
}
342
357
343
358
public async Task < ( byte , SubscribeResponse ) > Subscribe ( string stream , IOffsetType offsetType ,
@@ -386,20 +401,24 @@ await Request<DeletePublisherRequest, DeletePublisherResponse>(corr =>
386
401
return ( subscriptionId , response ) ;
387
402
}
388
403
389
- public async Task < UnsubscribeResponse > Unsubscribe ( byte subscriptionId )
404
+ public async Task < UnsubscribeResponse > Unsubscribe ( byte subscriptionId , bool ignoreIfAlreadyRemoved = false )
390
405
{
391
406
await _poolSemaphore . WaitAsync ( ) . ConfigureAwait ( false ) ;
392
407
try
393
408
{
394
- // here we reduce a bit the timeout to avoid waiting too much
395
- // if the client is busy with read operations it can take time to process the unsubscribe
396
- // but the subscribe is removed.
397
- var result =
398
- await Request < UnsubscribeRequest , UnsubscribeResponse > ( corr =>
399
- new UnsubscribeRequest ( corr , subscriptionId ) , TimeSpan . FromSeconds ( 5 ) ) . ConfigureAwait ( false ) ;
400
- _logger . LogDebug ( "Unsubscribe request : {SubscriptionId}" , subscriptionId ) ;
401
-
402
- return result ;
409
+ if ( ! ignoreIfAlreadyRemoved )
410
+ {
411
+ // here we reduce a bit the timeout to avoid waiting too much
412
+ // if the client is busy with read operations it can take time to process the unsubscribe
413
+ // but the subscribe is removed.
414
+ var result =
415
+ await Request < UnsubscribeRequest , UnsubscribeResponse > ( corr =>
416
+ new UnsubscribeRequest ( corr , subscriptionId ) ,
417
+ TimeSpan . FromSeconds ( 5 ) ) . ConfigureAwait ( false ) ;
418
+ _logger . LogDebug ( "Unsubscribe request : {SubscriptionId}" , subscriptionId ) ;
419
+
420
+ return result ;
421
+ }
403
422
}
404
423
finally
405
424
{
@@ -408,6 +427,8 @@ await Request<UnsubscribeRequest, UnsubscribeResponse>(corr =>
408
427
consumers . Remove ( subscriptionId ) ;
409
428
_poolSemaphore . Release ( ) ;
410
429
}
430
+
431
+ return new UnsubscribeResponse ( ) ;
411
432
}
412
433
413
434
public async Task < PartitionsQueryResponse > QueryPartition ( string superStream )
@@ -477,12 +498,25 @@ private async Task HandleIncoming(Memory<byte> frameMemory)
477
498
case PublishConfirm . Key :
478
499
PublishConfirm . Read ( frame , out var confirm ) ;
479
500
confirmFrames += 1 ;
480
- var ( confirmCallback , _) = publishers [ confirm . PublisherId ] ;
481
- confirmCallback ( confirm . PublishingIds ) ;
482
- if ( MemoryMarshal . TryGetArray ( confirm . PublishingIds , out var confirmSegment ) )
501
+ if ( publishers . TryGetValue ( confirm . PublisherId , out var publisherConf ) )
483
502
{
484
- if ( confirmSegment . Array != null )
485
- ArrayPool < ulong > . Shared . Return ( confirmSegment . Array ) ;
503
+ var ( confirmCallback , _) = publisherConf ;
504
+ confirmCallback ( confirm . PublishingIds ) ;
505
+ if ( MemoryMarshal . TryGetArray ( confirm . PublishingIds , out var confirmSegment ) )
506
+ {
507
+ if ( confirmSegment . Array != null )
508
+ ArrayPool < ulong > . Shared . Return ( confirmSegment . Array ) ;
509
+ }
510
+ }
511
+ else
512
+ {
513
+ // the producer is not found, this can happen when the producer is closing
514
+ // and there are still confirmation on the wire
515
+ // we can ignore the error since the producer does not exists anymore
516
+ _logger ? . LogDebug (
517
+ "Could not find stream producer {ID} or producer is closing." +
518
+ "A possible cause it that the producer was closed and the are still confirmation on the wire. " ,
519
+ confirm . PublisherId ) ;
486
520
}
487
521
488
522
break ;
@@ -507,12 +541,26 @@ private async Task HandleIncoming(Memory<byte> frameMemory)
507
541
break ;
508
542
case PublishError . Key :
509
543
PublishError . Read ( frame , out var error ) ;
510
- var ( _, errorCallback ) = publishers [ error . PublisherId ] ;
511
- errorCallback ( error . PublishingErrors ) ;
544
+ if ( publishers . TryGetValue ( error . PublisherId , out var publisher ) )
545
+ {
546
+ var ( _, errorCallback ) = publisher ;
547
+ errorCallback ( error . PublishingErrors ) ;
548
+ }
549
+ else
550
+ {
551
+ // the producer is not found, this can happen when the producer is closing
552
+ // and there are still confirmation on the wire
553
+ // we can ignore the error since the producer does not exists anymore
554
+ _logger ? . LogDebug (
555
+ "Could not find stream producer {ID} or producer is closing." +
556
+ "A possible cause it that the producer was closed and the are still confirmation on the wire. " ,
557
+ error . PublisherId ) ;
558
+ }
559
+
512
560
break ;
513
561
case MetaDataUpdate . Key :
514
562
MetaDataUpdate . Read ( frame , out var metaDataUpdate ) ;
515
- Parameters . MetadataHandler ( metaDataUpdate ) ;
563
+ Parameters . FireMetadataUpdate ( metaDataUpdate ) ;
516
564
break ;
517
565
case TuneResponse . Key :
518
566
TuneResponse . Read ( frame , out var tuneResponse ) ;
0 commit comments