@@ -196,7 +196,7 @@ public virtual bool IsClosed
196
196
/// The total number of events that are currently buffered and waiting to be published, across all partitions.
197
197
/// </summary>
198
198
///
199
- public virtual int TotalBufferedEventCount => _totalBufferedEventCount ;
199
+ public virtual int TotalBufferedEventCount => Volatile . Read ( ref _totalBufferedEventCount ) ;
200
200
201
201
/// <summary>
202
202
/// The instance of <see cref="EventHubsEventSource" /> which can be mocked for testing.
@@ -603,7 +603,7 @@ private EventHubBufferedProducerClient(string fullyQualifiedNamespace,
603
603
{
604
604
TokenCredential tokenCred => new EventHubProducerClient ( fullyQualifiedNamespace , eventHubName , tokenCred , options ) ,
605
605
AzureSasCredential sasCred => new EventHubProducerClient ( fullyQualifiedNamespace , eventHubName , sasCred , options ) ,
606
- AzureNamedKeyCredential keyCred => new EventHubProducerClient ( fullyQualifiedNamespace , eventHubName , keyCred , options ) ,
606
+ AzureNamedKeyCredential keyCred => new EventHubProducerClient ( fullyQualifiedNamespace , eventHubName , keyCred , options ) ,
607
607
_ => throw new ArgumentException ( Resources . UnsupportedCredential , nameof ( credential ) )
608
608
} ;
609
609
@@ -772,13 +772,13 @@ public virtual async Task<int> EnqueueEventAsync(EventData eventData,
772
772
773
773
if ( ( ! IsPublishing ) || ( _producerManagementTask ? . IsCompleted ?? false ) )
774
774
{
775
- try
775
+ if ( ! _stateGuard . Wait ( 0 , cancellationToken ) )
776
776
{
777
- if ( ! _stateGuard . Wait ( 0 , cancellationToken ) )
778
- {
779
- await _stateGuard . WaitAsync ( cancellationToken ) . ConfigureAwait ( false ) ;
780
- }
777
+ await _stateGuard . WaitAsync ( cancellationToken ) . ConfigureAwait ( false ) ;
778
+ }
781
779
780
+ try
781
+ {
782
782
Argument . AssertNotClosed ( _isClosed , nameof ( EventHubBufferedProducerClient ) ) ;
783
783
784
784
// StartPublishingAsync will verify that publishing is not already taking
@@ -935,13 +935,13 @@ public virtual async Task<int> EnqueueEventsAsync(IEnumerable<EventData> events,
935
935
936
936
if ( ( ! IsPublishing ) || ( _producerManagementTask ? . IsCompleted ?? false ) )
937
937
{
938
- try
938
+ if ( ! _stateGuard . Wait ( 0 , cancellationToken ) )
939
939
{
940
- if ( ! _stateGuard . Wait ( 0 , cancellationToken ) )
941
- {
942
- await _stateGuard . WaitAsync ( cancellationToken ) . ConfigureAwait ( false ) ;
943
- }
940
+ await _stateGuard . WaitAsync ( cancellationToken ) . ConfigureAwait ( false ) ;
941
+ }
944
942
943
+ try
944
+ {
945
945
Argument . AssertNotClosed ( _isClosed , nameof ( EventHubBufferedProducerClient ) ) ;
946
946
947
947
// StartPublishingAsync will verify that publishing is not already taking
@@ -1059,6 +1059,15 @@ public virtual async Task FlushAsync(CancellationToken cancellationToken = defau
1059
1059
1060
1060
await StopPublishingAsync ( cancelActiveSendOperations : false , cancellationToken ) . ConfigureAwait ( false ) ;
1061
1061
await FlushInternalAsync ( cancellationToken ) . ConfigureAwait ( false ) ;
1062
+
1063
+ // There's an unlikely race condition where it is possible that an event batch was being enqueued before publishing
1064
+ // stopped and was not written to the partition buffer until after the flush for that partition completed. To guard
1065
+ // against this, restart publishing if any events are pending.
1066
+
1067
+ if ( Volatile . Read ( ref _totalBufferedEventCount ) > 0 )
1068
+ {
1069
+ await StartPublishingAsync ( cancellationToken ) . ConfigureAwait ( false ) ;
1070
+ }
1062
1071
}
1063
1072
finally
1064
1073
{
@@ -1092,15 +1101,13 @@ public virtual async Task CloseAsync(bool flush = true,
1092
1101
1093
1102
var capturedExceptions = default ( List < Exception > ) ;
1094
1103
1095
- try
1104
+ if ( ! _stateGuard . Wait ( 0 , cancellationToken ) )
1096
1105
{
1097
- if ( ! _stateGuard . Wait ( 0 , cancellationToken ) )
1098
- {
1099
- await _stateGuard . WaitAsync ( cancellationToken ) . ConfigureAwait ( false ) ;
1100
- }
1101
-
1102
- // If we've reached this point without an exception, the guard is held.
1106
+ await _stateGuard . WaitAsync ( cancellationToken ) . ConfigureAwait ( false ) ;
1107
+ }
1103
1108
1109
+ try
1110
+ {
1104
1111
if ( _isClosed )
1105
1112
{
1106
1113
return ;
@@ -1675,7 +1682,7 @@ internal virtual async Task DrainAndPublishPartitionEvents(PartitionPublishingSt
1675
1682
}
1676
1683
1677
1684
var partitionStateDelta = ( partitionState . BufferedEventCount * - 1 ) ;
1678
- var totalDeltaZero = ( _totalBufferedEventCount * - 1 ) ;
1685
+ var totalDeltaZero = ( Volatile . Read ( ref _totalBufferedEventCount ) * - 1 ) ;
1679
1686
1680
1687
Interlocked . Add ( ref _totalBufferedEventCount , Math . Max ( totalDeltaZero , partitionStateDelta ) ) ;
1681
1688
Interlocked . Exchange ( ref partitionState . BufferedEventCount , 0 ) ;
@@ -1840,7 +1847,7 @@ private async Task StartPublishingAsync(CancellationToken cancellationToken)
1840
1847
// If there is already a task running for the background management process,
1841
1848
// then no further initialization is needed.
1842
1849
1843
- if ( ( IsPublishing ) & & ( ! _producerManagementTask . IsCompleted ) )
1850
+ if ( ( IsPublishing ) & & ( ! _producerManagementTask . IsCompleted ) || ( _isClosed ) )
1844
1851
{
1845
1852
return ;
1846
1853
}
@@ -1954,13 +1961,12 @@ private async Task StopPublishingAsync(bool cancelActiveSendOperations,
1954
1961
1955
1962
_producerManagementTask = null ;
1956
1963
}
1964
+ catch ( OperationCanceledException ex ) when ( ex is not TaskCanceledException )
1965
+ {
1966
+ throw new TaskCanceledException ( ex . Message , ex ) ;
1967
+ }
1957
1968
catch ( Exception ex )
1958
1969
{
1959
- if ( ex is OperationCanceledException opEx )
1960
- {
1961
- throw new TaskCanceledException ( opEx . Message , opEx ) ;
1962
- }
1963
-
1964
1970
Logger . BufferedProducerBackgroundProcessingStopError ( Identifier , EventHubName , ex . Message ) ;
1965
1971
( capturedExceptions ??= new List < Exception > ( ) ) . Add ( ex ) ;
1966
1972
}
@@ -2247,7 +2253,7 @@ private Task RunPublishingAsync(CancellationToken cancellationToken) =>
2247
2253
if ( ( ! cancellationToken . IsCancellationRequested )
2248
2254
&& ( _activePartitionStateMap . TryGetValue ( partition , out var partitionState ) )
2249
2255
&& ( partitionState . BufferedEventCount > 0 )
2250
- && ( ( partitionState . PartitionGuard . Wait ( 0 , cancellationToken ) ) || ( await partitionState . PartitionGuard . WaitAsync ( PartitionPublishingGuardAcquireLimitMilliseconds , cancellationToken ) . ConfigureAwait ( ( false ) ) ) ) )
2256
+ && ( ( partitionState . PartitionGuard . Wait ( 0 , cancellationToken ) ) || ( await partitionState . PartitionGuard . WaitAsync ( PartitionPublishingGuardAcquireLimitMilliseconds , cancellationToken ) . ConfigureAwait ( false ) ) ) )
2251
2257
{
2252
2258
// Responsibility for releasing the guard semaphore is passed to the task.
2253
2259
@@ -2257,7 +2263,7 @@ private Task RunPublishingAsync(CancellationToken cancellationToken) =>
2257
2263
// If there are no events in the buffer, avoid a tight loop by blocking to wait for events to be enqueued
2258
2264
// after a small delay.
2259
2265
2260
- if ( _totalBufferedEventCount == 0 )
2266
+ if ( Volatile . Read ( ref _totalBufferedEventCount ) == 0 )
2261
2267
{
2262
2268
// If completion source doesn't exist or was already set, then swap in a new completion source to be
2263
2269
// set when an event is enqueued. Allow the publishing loop to tick for one additional check of the
@@ -2281,10 +2287,15 @@ private Task RunPublishingAsync(CancellationToken cancellationToken) =>
2281
2287
2282
2288
var idleWatch = ValueStopwatch . StartNew ( ) ;
2283
2289
2284
- await _eventEnqueuedCompletionSource . Task . AwaitWithCancellation ( cancellationToken ) ;
2285
- _eventEnqueuedCompletionSource = null ;
2286
-
2287
- Logger . BufferedProducerIdleComplete ( Identifier , EventHubName , operationId , idleWatch . GetElapsedTime ( ) . TotalSeconds ) ;
2290
+ try
2291
+ {
2292
+ await _eventEnqueuedCompletionSource . Task . AwaitWithCancellation ( cancellationToken ) ;
2293
+ _eventEnqueuedCompletionSource = null ;
2294
+ }
2295
+ finally
2296
+ {
2297
+ Logger . BufferedProducerIdleComplete ( Identifier , EventHubName , operationId , idleWatch . GetElapsedTime ( ) . TotalSeconds ) ;
2298
+ }
2288
2299
}
2289
2300
}
2290
2301
}
@@ -2332,7 +2343,7 @@ private Task RunPublishingAsync(CancellationToken cancellationToken) =>
2332
2343
/// set of Event Hub partitions has changed since they were last queried.
2333
2344
/// </summary>
2334
2345
///
2335
- /// <param name="cancellationToken">A <see cref="CancellationToken" /> instance to signal the request to cancel the operation.</param>
2346
+ /// v
2336
2347
///
2337
2348
/// <remarks>
2338
2349
/// This method will potentially modify class state, overwriting the tracked set of partitions.
@@ -2468,12 +2479,12 @@ internal sealed class PartitionPublishingState : IDisposable
2468
2479
/// <summary>The writer to use for enqueuing events to be published.</summary>
2469
2480
public ChannelWriter < EventData > PendingEventsWriter => _pendingEvents . Writer ;
2470
2481
2471
- /// <summary>The identifier of the partition that is being published.</summary>
2472
- public readonly string PartitionId ;
2473
-
2474
2482
/// <summary>The primitive for synchronizing access for publishing to the partition.</summary>
2475
2483
public readonly SemaphoreSlim PartitionGuard ;
2476
2484
2485
+ /// <summary>The identifier of the partition that is being published.</summary>
2486
+ public readonly string PartitionId ;
2487
+
2477
2488
/// <summary>The number of events that are currently buffered and waiting to be published for this partition.</summary>
2478
2489
public int BufferedEventCount ;
2479
2490
0 commit comments