/// <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 default wait time specified when the consumer was created 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 override async Task <IEnumerable <EventData> > ReceiveAsync(int maximumMessageCount, TimeSpan?maximumWaitTime, CancellationToken cancellationToken) { Argument.AssertNotClosed(_closed, nameof(AmqpEventHubConsumer)); Argument.AssertAtLeast(maximumMessageCount, 1, nameof(maximumMessageCount)); var receivedEventCount = 0; var failedAttemptCount = 0; var waitTime = (maximumWaitTime ?? _tryTimeout); var link = default(ReceivingAmqpLink); var retryDelay = default(TimeSpan?); var amqpMessages = default(IEnumerable <AmqpMessage>); var receivedEvents = default(List <EventData>); var stopWatch = Stopwatch.StartNew(); try { while (!cancellationToken.IsCancellationRequested) { try { EventHubsEventSource.Log.EventReceiveStart(EventHubName, ConsumerGroup, PartitionId); link = await ReceiveLink.GetOrCreateAsync(UseMinimum(ConnectionScope.SessionTimeout, _tryTimeout)).ConfigureAwait(false); cancellationToken.ThrowIfCancellationRequested <TaskCanceledException>(); var messagesReceived = await Task.Factory.FromAsync ( (callback, state) => link.BeginReceiveMessages(maximumMessageCount, waitTime, callback, state), (asyncResult) => link.EndReceiveMessages(asyncResult, out amqpMessages), TaskCreationOptions.RunContinuationsAsynchronously ).ConfigureAwait(false); cancellationToken.ThrowIfCancellationRequested <TaskCanceledException>(); // If event messages were received, then package them for consumption and // return them. if ((messagesReceived) && (amqpMessages != null)) { receivedEvents ??= new List <EventData>(); foreach (AmqpMessage message in amqpMessages) { link.DisposeDelivery(message, true, AmqpConstants.AcceptedOutcome); receivedEvents.Add(MessageConverter.CreateEventFromMessage(message)); message.Dispose(); } receivedEventCount = receivedEvents.Count; if ((Options.TrackLastEnqueuedEventInformation) && (receivedEventCount > 0)) { LastReceivedEvent = receivedEvents[receivedEventCount - 1]; } return(receivedEvents); } // No events were available. return(Enumerable.Empty <EventData>()); } catch (EventHubsTimeoutException) { // Because the timeout specified with the request is intended to be the maximum // amount of time to wait for events, a timeout isn't considered an error condition, // rather a sign that no events were available in the requested period. return(Enumerable.Empty <EventData>()); } catch (AmqpException amqpException) { throw AmqpError.CreateExceptionForError(amqpException.Error, EventHubName); } catch (Exception ex) { // Determine if there should be a retry for the next attempt; if so enforce the delay but do not quit the loop. // Otherwise, bubble the exception. ++failedAttemptCount; retryDelay = _retryPolicy.CalculateRetryDelay(ex, failedAttemptCount); if ((retryDelay.HasValue) && (!ConnectionScope.IsDisposed) && (!cancellationToken.IsCancellationRequested)) { EventHubsEventSource.Log.EventReceiveError(EventHubName, ConsumerGroup, PartitionId, ex.Message); await Task.Delay(UseMinimum(retryDelay.Value, waitTime.CalculateRemaining(stopWatch.Elapsed)), cancellationToken).ConfigureAwait(false); } else { throw; } } } // If no value has been returned nor exception thrown by this point, // then cancellation has been requested. throw new TaskCanceledException(); } catch (Exception ex) { EventHubsEventSource.Log.EventReceiveError(EventHubName, ConsumerGroup, PartitionId, ex.Message); throw; } finally { stopWatch.Stop(); EventHubsEventSource.Log.EventReceiveComplete(EventHubName, ConsumerGroup, PartitionId, receivedEventCount); } }
/// <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 set is returned.</returns> /// public override async Task <IReadOnlyList <EventData> > ReceiveAsync(int maximumMessageCount, TimeSpan?maximumWaitTime, CancellationToken cancellationToken) { Argument.AssertNotClosed(_closed, nameof(AmqpConsumer)); Argument.AssertAtLeast(maximumMessageCount, 1, nameof(maximumMessageCount)); var receivedEventCount = 0; var failedAttemptCount = 0; var tryTimeout = RetryPolicy.CalculateTryTimeout(0); var waitTime = (maximumWaitTime ?? tryTimeout); var link = default(ReceivingAmqpLink); var retryDelay = default(TimeSpan?); var amqpMessages = default(IEnumerable <AmqpMessage>); var receivedEvents = default(List <EventData>); var lastReceivedEvent = default(EventData); var stopWatch = ValueStopwatch.StartNew(); try { while ((!cancellationToken.IsCancellationRequested) && (!_closed)) { try { // Creation of the link happens without explicit knowledge of the cancellation token // used for this operation; validate the token state before attempting link creation and // again after the operation completes to provide best efforts in respecting it. EventHubsEventSource.Log.EventReceiveStart(EventHubName, ConsumerGroup, PartitionId); cancellationToken.ThrowIfCancellationRequested <TaskCanceledException>(); link = await ReceiveLink.GetOrCreateAsync(UseMinimum(ConnectionScope.SessionTimeout, tryTimeout)).ConfigureAwait(false); cancellationToken.ThrowIfCancellationRequested <TaskCanceledException>(); var messagesReceived = await Task.Factory.FromAsync ( (callback, state) => link.BeginReceiveMessages(maximumMessageCount, waitTime, callback, state), (asyncResult) => link.EndReceiveMessages(asyncResult, out amqpMessages), TaskCreationOptions.RunContinuationsAsynchronously ).ConfigureAwait(false); cancellationToken.ThrowIfCancellationRequested <TaskCanceledException>(); // If event messages were received, then package them for consumption and // return them. if ((messagesReceived) && (amqpMessages != null)) { receivedEvents ??= new List <EventData>(); foreach (AmqpMessage message in amqpMessages) { link.DisposeDelivery(message, true, AmqpConstants.AcceptedOutcome); receivedEvents.Add(MessageConverter.CreateEventFromMessage(message)); message.Dispose(); } receivedEventCount = receivedEvents.Count; if (receivedEventCount > 0) { lastReceivedEvent = receivedEvents[receivedEventCount - 1]; if (lastReceivedEvent.Offset > long.MinValue) { CurrentEventPosition = EventPosition.FromOffset(lastReceivedEvent.Offset, false); } if (TrackLastEnqueuedEventProperties) { LastReceivedEvent = lastReceivedEvent; } } return(receivedEvents); } // No events were available. return(EmptyEventSet); } catch (EventHubsException ex) when(ex.Reason == EventHubsException.FailureReason.ServiceTimeout) { // Because the timeout specified with the request is intended to be the maximum // amount of time to wait for events, a timeout isn't considered an error condition, // rather a sign that no events were available in the requested period. return(EmptyEventSet); } catch (Exception ex) { Exception activeEx = ex.TranslateServiceException(EventHubName); // Determine if there should be a retry for the next attempt; if so enforce the delay but do not quit the loop. // Otherwise, bubble the exception. ++failedAttemptCount; retryDelay = RetryPolicy.CalculateRetryDelay(activeEx, failedAttemptCount); if ((retryDelay.HasValue) && (!ConnectionScope.IsDisposed) && (!cancellationToken.IsCancellationRequested)) { EventHubsEventSource.Log.EventReceiveError(EventHubName, ConsumerGroup, PartitionId, activeEx.Message); await Task.Delay(UseMinimum(retryDelay.Value, waitTime.CalculateRemaining(stopWatch.GetElapsedTime())), cancellationToken).ConfigureAwait(false); tryTimeout = RetryPolicy.CalculateTryTimeout(failedAttemptCount); } else if (ex is AmqpException) { ExceptionDispatchInfo.Capture(activeEx).Throw(); } else { throw; } } } // If no value has been returned nor exception thrown by this point, // then cancellation has been requested. throw new TaskCanceledException(); } catch (TaskCanceledException) { throw; } catch (Exception ex) { EventHubsEventSource.Log.EventReceiveError(EventHubName, ConsumerGroup, PartitionId, ex.Message); throw; } finally { EventHubsEventSource.Log.EventReceiveComplete(EventHubName, ConsumerGroup, PartitionId, receivedEventCount); } }
/// <summary> /// Receives a batch of <see cref="EventData" /> from the Event Hub partition. /// </summary> /// /// <param name="maximumEventCount">The maximum number of messages to receive in this batch.</param> /// <param name="maximumWaitTime">The maximum amount of time to wait for events to become available, if no events can be read from the prefetch queue. 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 set is returned.</returns> /// /// <remarks> /// When events are available in the prefetch queue, they will be used to form the batch as quickly as possible without waiting for additional events from the /// Event Hubs service to try and meet the requested <paramref name="maximumEventCount" />. When no events are available in prefetch, the receiver will wait up /// to the <paramref name="maximumWaitTime"/> for events to be read from the service. Once any events are available, they will be used to form the batch immediately. /// </remarks> /// public override async Task <IReadOnlyList <EventData> > ReceiveAsync(int maximumEventCount, TimeSpan?maximumWaitTime, CancellationToken cancellationToken) { Argument.AssertNotClosed(_closed, nameof(AmqpConsumer)); Argument.AssertNotClosed(ConnectionScope.IsDisposed, nameof(EventHubConnection)); Argument.AssertAtLeast(maximumEventCount, 1, nameof(maximumEventCount)); var receivedEventCount = 0; var failedAttemptCount = 0; var tryTimeout = RetryPolicy.CalculateTryTimeout(0); var waitTime = (maximumWaitTime ?? tryTimeout); var operationId = Guid.NewGuid().ToString("D", CultureInfo.InvariantCulture); var link = default(ReceivingAmqpLink); var retryDelay = default(TimeSpan?); var receivedEvents = default(List <EventData>); var lastReceivedEvent = default(EventData); var stopWatch = ValueStopwatch.StartNew(); try { while ((!cancellationToken.IsCancellationRequested) && (!_closed)) { try { // Creation of the link happens without explicit knowledge of the cancellation token // used for this operation; validate the token state before attempting link creation and // again after the operation completes to provide best efforts in respecting it. EventHubsEventSource.Log.EventReceiveStart(EventHubName, ConsumerGroup, PartitionId, operationId); link = await ReceiveLink.GetOrCreateAsync(UseMinimum(ConnectionScope.SessionTimeout, tryTimeout), cancellationToken).ConfigureAwait(false); cancellationToken.ThrowIfCancellationRequested <TaskCanceledException>(); var messagesReceived = await link.ReceiveMessagesAsync(maximumEventCount, ReceiveBuildBatchInterval, waitTime, cancellationToken).ConfigureAwait(false); // If no messages were received, then just return the empty set. if (messagesReceived == null) { return(EmptyEventSet); } // If event messages were received, then package them for consumption and // return them. foreach (AmqpMessage message in messagesReceived) { receivedEvents ??= new List <EventData>(); link.DisposeDelivery(message, true, AmqpConstants.AcceptedOutcome); receivedEvents.Add(MessageConverter.CreateEventFromMessage(message)); message.Dispose(); receivedEventCount = receivedEvents.Count; } if (receivedEventCount > 0) { lastReceivedEvent = receivedEvents[receivedEventCount - 1]; if (lastReceivedEvent.Offset > long.MinValue) { CurrentEventPosition = EventPosition.FromOffset(lastReceivedEvent.Offset, false); } if (TrackLastEnqueuedEventProperties) { LastReceivedEvent = lastReceivedEvent; } } return(receivedEvents ?? EmptyEventSet); } catch (EventHubsException ex) when(ex.Reason == EventHubsException.FailureReason.ServiceTimeout) { // Because the timeout specified with the request is intended to be the maximum // amount of time to wait for events, a timeout isn't considered an error condition, // rather a sign that no events were available in the requested period. return(EmptyEventSet); } catch (Exception ex) { Exception activeEx = ex.TranslateServiceException(EventHubName); // If the partition was stolen determine the correct action to take for // capturing it with respect to whether the consumer should be invalidated. // // In either case, it is a terminal exception and will not trigger a retry; // allow the normal error handling flow to surface the exception. if (ex.IsConsumerPartitionStolenException()) { // If the consumer should be invalidated, capture the exception // and force-close the link. This will ensure that the next operation // will surface it. if (InvalidateConsumerWhenPartitionStolen) { _activePartitionStolenException = ex; CloseConsumerLink(link); } else { // If the consumer should not be invalidated, clear any previously captured exception to avoid // surfacing the failure multiple times. If the link is stolen after this operation, it will // be intercepted and handled as needed. _activePartitionStolenException = null; } } // Determine if there should be a retry for the next attempt; if so enforce the delay but do not quit the loop. // Otherwise, bubble the exception. ++failedAttemptCount; retryDelay = RetryPolicy.CalculateRetryDelay(activeEx, failedAttemptCount); if ((retryDelay.HasValue) && (!ConnectionScope.IsDisposed) && (!_closed) && (!cancellationToken.IsCancellationRequested)) { EventHubsEventSource.Log.EventReceiveError(EventHubName, ConsumerGroup, PartitionId, operationId, activeEx.Message); await Task.Delay(UseMinimum(retryDelay.Value, waitTime.CalculateRemaining(stopWatch.GetElapsedTime())), cancellationToken).ConfigureAwait(false); tryTimeout = RetryPolicy.CalculateTryTimeout(failedAttemptCount); } else if (ex is AmqpException) { ExceptionDispatchInfo.Capture(activeEx).Throw(); } else { throw; } } } // If no value has been returned nor exception thrown by this point, // then cancellation has been requested. throw new TaskCanceledException(); } catch (TaskCanceledException) { throw; } catch (Exception ex) { EventHubsEventSource.Log.EventReceiveError(EventHubName, ConsumerGroup, PartitionId, operationId, ex.Message); throw; } finally { EventHubsEventSource.Log.EventReceiveComplete(EventHubName, ConsumerGroup, PartitionId, operationId, failedAttemptCount, receivedEventCount); } }