/// <summary> /// The main loop of a partition pump. It receives events from the Azure Event Hubs service /// and delegates their processing to the inner partition processor. /// </summary> /// /// <param name="cancellationToken">A <see cref="CancellationToken"/> instance to signal the request to cancel the operation.</param> /// /// <returns>A task to be resolved on when the operation has completed.</returns> /// private async Task RunAsync(CancellationToken cancellationToken) { while (!cancellationToken.IsCancellationRequested) { IEnumerable <EventData> receivedEvents = null; try { receivedEvents = await InnerConsumer.ReceiveAsync(Options.MaximumMessageCount, Options.MaximumReceiveWaitTime, cancellationToken).ConfigureAwait(false); } catch (Exception exception) { await PartitionProcessor.ProcessErrorAsync(exception, cancellationToken).ConfigureAwait(false); // Stop the pump if it's not a retryable exception. if (RetryPolicy.CalculateRetryDelay(exception, 1) == null) { // StopAsync cannot be awaited in this method because it awaits RunningTask, so we would have a deadlock. // For this reason, StopAsync starts to run concurrently with this task. _ = StopAsync(PartitionProcessorCloseReason.EventHubException); break; } } await PartitionProcessor.ProcessEventsAsync(receivedEvents, cancellationToken).ConfigureAwait(false); } }
/// <summary> /// Stops the partition pump. In case it isn't running, nothing happens. /// </summary> /// /// <returns>A task to be resolved on when the operation has completed.</returns> /// public async Task StopAsync() { if (RunningTask != null) { await RunningTaskSemaphore.WaitAsync().ConfigureAwait(false); try { if (RunningTask != null) { RunningTaskTokenSource.Cancel(); RunningTaskTokenSource = null; try { // In case the pump has failed, don't catch the unhandled exception and let the caller handle it. await RunningTask.ConfigureAwait(false); } finally { RunningTask = null; await InnerConsumer.CloseAsync().ConfigureAwait(false); } } } finally { RunningTaskSemaphore.Release(); } } }
/// <summary> /// Stops the partition pump. In case it hasn't been started, nothing happens. /// </summary> /// /// <param name="reason">The reason why the partition pump is being closed.</param> /// /// <returns>A task to be resolved on when the operation has completed.</returns> /// private async Task StopAsync(PartitionProcessorCloseReason reason) { if (RunningTask != null) { await RunningTaskSemaphore.WaitAsync().ConfigureAwait(false); try { if (RunningTask != null) { RunningTaskTokenSource.Cancel(); RunningTaskTokenSource = null; try { await RunningTask.ConfigureAwait(false); } finally { RunningTask = null; } await InnerConsumer.CloseAsync().ConfigureAwait(false); InnerConsumer = null; await PartitionProcessor.CloseAsync(reason).ConfigureAwait(false); } } finally { RunningTaskSemaphore.Release(); } } }
/// <summary> /// Stops the partition pump. In case it isn't running, nothing happens. /// </summary> /// /// <param name="reason">The reason why the processing for the associated partition is being stopped. In case it's <c>null</c>, the internal close reason set by this pump is used.</param> /// /// <returns>A task to be resolved on when the operation has completed.</returns> /// public async Task StopAsync(CloseReason?reason) { if (RunningTask != null) { await RunningTaskSemaphore.WaitAsync().ConfigureAwait(false); try { if (RunningTask != null) { RunningTaskTokenSource.Cancel(); RunningTaskTokenSource = null; try { // RunningTask is only expected to fail when the event processor throws while processing // an error, but unforeseen scenarios might happen. await RunningTask.ConfigureAwait(false); } catch (Exception) { // TODO: delegate the exception handling to an Exception Callback. } RunningTask = null; // It's important to close the consumer as soon as possible. Failing to do so multiple times // would make it impossible to create more consumers for the associated partition as there's a // limit per client. await InnerConsumer.CloseAsync().ConfigureAwait(false); // In case an exception is encountered while the processing is stopping, don't catch it and let // the event processor handle it. The pump has no way to guess when a partition was lost or when // a shutdown request was sent to the event processor, so it expects a "reason" parameter to provide // this information. However, in case of pump failure, the external event processor does not have // enough information to figure out what failure reason to use, as this information is only known // by the pump. In this case, we expect the processor-provided reason to be null, and the private // CloseReason is used instead. if (OwnerEventProcessor.ProcessingForPartitionStoppedAsync != null) { var stopContext = new PartitionProcessingStoppedContext(Context, reason ?? CloseReason); await OwnerEventProcessor.ProcessingForPartitionStoppedAsync(stopContext).ConfigureAwait(false); } } } finally { RunningTaskSemaphore.Release(); } } }
/// <summary> /// Receives a batch of <see cref="EventData" /> from the Event Hub partition. /// </summary> /// /// <param name="maximumMessageCount">The maximum number of messages to receive in this batch.</param> /// <param name="maximumWaitTime">The maximum amount of time to wait to build up the requested message count for the batch; if not specified, the per-try timeout specified by the retry policy will be used.</param> /// <param name="cancellationToken">An optional <see cref="CancellationToken"/> instance to signal the request to cancel the operation.</param> /// /// <returns>The batch of <see cref="EventData" /> from the Event Hub partition this consumer is associated with. If no events are present, an empty enumerable is returned.</returns> /// public virtual Task <IEnumerable <EventData> > ReceiveAsync(int maximumMessageCount, TimeSpan?maximumWaitTime = default, CancellationToken cancellationToken = default) { maximumWaitTime ??= DefaultMaximumReceiveWaitTime; Argument.AssertInRange(maximumMessageCount, 1, int.MaxValue, nameof(maximumMessageCount)); Argument.AssertNotNegative(maximumWaitTime.Value, nameof(maximumWaitTime)); return(InnerConsumer.ReceiveAsync(maximumMessageCount, maximumWaitTime.Value, cancellationToken)); }
/// <summary> /// Closes the receiver. /// </summary> /// /// <param name="cancellationToken">An optional <see cref="CancellationToken"/> instance to signal the request to cancel the operation.</param> /// /// <returns>A task to be resolved on when the operation has completed.</returns> /// public virtual async Task CloseAsync(CancellationToken cancellationToken = default) { IsClosed = true; try { await InnerConsumer.CloseAsync(cancellationToken).ConfigureAwait(false); } catch (Exception ex) when(ex is TaskCanceledException || ex is OperationCanceledException) { IsClosed = InnerConsumer.IsClosed; throw; } }
/// <summary> /// The main loop of a partition pump. It receives events from the Azure Event Hubs service /// and delegates their processing to the inner partition processor. /// </summary> /// /// <param name="cancellationToken">A <see cref="CancellationToken"/> instance to signal the request to cancel the operation.</param> /// /// <returns>A task to be resolved on when the operation has completed.</returns> /// private async Task RunAsync(CancellationToken cancellationToken) { IEnumerable <EventData> receivedEvents; Exception unrecoverableException = null; // We'll break from the loop upon encountering a non-retriable exception. The event processor periodically // checks its pumps' status, so it should be aware of when one of them stops working. while (!cancellationToken.IsCancellationRequested) { try { receivedEvents = await InnerConsumer.ReceiveAsync(Options.MaximumMessageCount, Options.MaximumReceiveWaitTime, cancellationToken).ConfigureAwait(false); try { await PartitionProcessor.ProcessEventsAsync(Context, receivedEvents, cancellationToken).ConfigureAwait(false); } catch (Exception partitionProcessorException) { unrecoverableException = partitionProcessorException; CloseReason = PartitionProcessorCloseReason.PartitionProcessorException; break; } } catch (Exception eventHubException) { // Stop running only if it's not a retriable exception. if (RetryPolicy.CalculateRetryDelay(eventHubException, 1) == null) { unrecoverableException = eventHubException; CloseReason = PartitionProcessorCloseReason.EventHubException; break; } } } if (unrecoverableException != null) { // In case an exception is encountered while partition processor is processing the error, don't // catch it and let the calling method (StopAsync) handle it. await PartitionProcessor.ProcessErrorAsync(Context, unrecoverableException, cancellationToken).ConfigureAwait(false); } }
/// <summary> /// The main loop of a partition pump. It receives events from the Azure Event Hubs service /// and delegates their processing to the event processor processing handlers. /// </summary> /// /// <param name="cancellationToken">A <see cref="CancellationToken"/> instance to signal the request to cancel the operation.</param> /// /// <returns>A task to be resolved on when the operation has completed.</returns> /// private async Task RunAsync(CancellationToken cancellationToken) { List <EventData> receivedEvents; Exception unrecoverableException = null; // We'll break from the loop upon encountering a non-retriable exception. The event processor periodically // checks its pumps' status, so it should be aware of when one of them stops working. while (!cancellationToken.IsCancellationRequested) { try { receivedEvents = (await InnerConsumer.ReceiveAsync(MaximumMessageCount, Options.MaximumReceiveWaitTime, cancellationToken).ConfigureAwait(false)).ToList(); using DiagnosticScope diagnosticScope = EventDataInstrumentation.ClientDiagnostics.CreateScope(DiagnosticProperty.EventProcessorProcessingActivityName); diagnosticScope.AddAttribute("kind", "server"); if (diagnosticScope.IsEnabled) { foreach (var eventData in receivedEvents) { if (EventDataInstrumentation.TryExtractDiagnosticId(eventData, out string diagnosticId)) { diagnosticScope.AddLink(diagnosticId); } } } // Small workaround to make sure we call ProcessEvent with EventData = null when no events have been received. // The code is expected to get simpler when we start using the async enumerator internally to receive events. if (receivedEvents.Count == 0) { receivedEvents.Add(null); } diagnosticScope.Start(); foreach (var eventData in receivedEvents) { try { var processorEvent = new EventProcessorEvent(Context, eventData, UpdateCheckpointAsync); await ProcessEventAsync(processorEvent).ConfigureAwait(false); } catch (Exception eventProcessingException) { diagnosticScope.Failed(eventProcessingException); unrecoverableException = eventProcessingException; break; } } } catch (Exception eventHubException) { // Stop running only if it's not a retriable exception. if (RetryPolicy.CalculateRetryDelay(eventHubException, 1) == null) { throw eventHubException; } } if (unrecoverableException != null) { throw unrecoverableException; } } }
/// <summary> /// The main loop of a partition pump. It receives events from the Azure Event Hubs service /// and delegates their processing to the inner partition processor. /// </summary> /// /// <param name="cancellationToken">A <see cref="CancellationToken"/> instance to signal the request to cancel the operation.</param> /// /// <returns>A task to be resolved on when the operation has completed.</returns> /// private async Task RunAsync(CancellationToken cancellationToken) { IEnumerable <EventData> receivedEvents; Exception unrecoverableException = null; // We'll break from the loop upon encountering a non-retriable exception. The event processor periodically // checks its pumps' status, so it should be aware of when one of them stops working. while (!cancellationToken.IsCancellationRequested) { try { receivedEvents = await InnerConsumer.ReceiveAsync(Options.MaximumMessageCount, Options.MaximumReceiveWaitTime, cancellationToken).ConfigureAwait(false); using DiagnosticScope diagnosticScope = EventDataInstrumentation.ClientDiagnostics.CreateScope(DiagnosticProperty.EventProcessorProcessingActivityName); diagnosticScope.AddAttribute("kind", "server"); if (diagnosticScope.IsEnabled) { foreach (var eventData in receivedEvents) { if (EventDataInstrumentation.TryExtractDiagnosticId(eventData, out string diagnosticId)) { diagnosticScope.AddLink(diagnosticId); } } } diagnosticScope.Start(); try { await PartitionProcessor.ProcessEventsAsync(Context, receivedEvents, cancellationToken).ConfigureAwait(false); } catch (Exception partitionProcessorException) { diagnosticScope.Failed(partitionProcessorException); unrecoverableException = partitionProcessorException; CloseReason = PartitionProcessorCloseReason.PartitionProcessorException; break; } } catch (Exception eventHubException) { // Stop running only if it's not a retriable exception. if (s_retryPolicy.CalculateRetryDelay(eventHubException, 1) == null) { unrecoverableException = eventHubException; CloseReason = PartitionProcessorCloseReason.EventHubException; break; } } } if (unrecoverableException != null) { // In case an exception is encountered while partition processor is processing the error, don't // catch it and let the calling method (StopAsync) handle it. await PartitionProcessor.ProcessErrorAsync(Context, unrecoverableException, cancellationToken).ConfigureAwait(false); } }