/// <summary> /// Invoked by the base implementation to determine the delay to wait until the next /// retry attempt. /// </summary> /// /// <param name="lastException">The last exception that was encountered; may be <c>null</c> if the retry was not triggered by an exception.</param> /// <param name="remainingTime">The remaining time for the overall operation timeout.</param> /// <param name="baseWaitTimeSecs">The base wait time for retries, in seconds.</param> /// <param name="retryCount">The number of retry attempts that have been made.</param> /// /// <returns>The duration to wait before retrying; or <c>null</c> if the operation should not be retried.</returns> /// protected override TimeSpan?OnGetNextRetryInterval(Exception lastException, TimeSpan remainingTime, int baseWaitTimeSecs, int retryCount) { // If there is no time remaining, do not retry. if (remainingTime <= TimeSpan.Zero) { return(null); } // Delegate to the track one code for eligibility to retry an exception, for // the sake of consistency. if (!TrackOne.RetryPolicy.IsRetryableException(lastException)) { return(null); } Exception mappedException = lastException switch { TrackOne.EventHubsException ex => ex.MapToTrackTwoException(), _ => lastException, }; TimeSpan?delay = _sourcePolicy.CalculateRetryDelay(mappedException, retryCount); if (delay > remainingTime) { return(null); } return(delay); }
/// <summary> /// Retrieves information about a specific partition for an Event Hub, including elements that describe the available /// events in the partition event stream. /// </summary> /// /// <param name="partitionId">The unique identifier of a partition associated with the Event Hub.</param> /// <param name="retryPolicy">The retry policy to use as the basis for retrieving the information.</param> /// <param name="cancellationToken">An optional <see cref="CancellationToken"/> instance to signal the request to cancel the operation.</param> /// /// <returns>The set of information for the requested partition under the Event Hub this client is associated with.</returns> /// public override async Task <PartitionProperties> GetPartitionPropertiesAsync(string partitionId, EventHubRetryPolicy retryPolicy, CancellationToken cancellationToken) { Argument.AssertNotClosed(_closed, nameof(AmqpClient)); Argument.AssertNotNullOrEmpty(partitionId, nameof(partitionId)); Argument.AssertNotNull(retryPolicy, nameof(retryPolicy)); var failedAttemptCount = 0; var retryDelay = default(TimeSpan?); var token = default(string); var link = default(RequestResponseAmqpLink); var stopWatch = Stopwatch.StartNew(); try { var tryTimeout = retryPolicy.CalculateTryTimeout(0); while (!cancellationToken.IsCancellationRequested) { try { EventHubsEventSource.Log.GetPartitionPropertiesStart(EventHubName, partitionId); // Create the request message and the management link. token = await AquireAccessTokenAsync(cancellationToken).ConfigureAwait(false); using AmqpMessage request = MessageConverter.CreatePartitionPropertiesRequest(EventHubName, partitionId, token); cancellationToken.ThrowIfCancellationRequested <TaskCanceledException>(); link = await ManagementLink.GetOrCreateAsync(UseMinimum(ConnectionScope.SessionTimeout, tryTimeout.CalculateRemaining(stopWatch.Elapsed))).ConfigureAwait(false); cancellationToken.ThrowIfCancellationRequested <TaskCanceledException>(); // Send the request and wait for the response. using AmqpMessage response = await link.RequestAsync(request, tryTimeout.CalculateRemaining(stopWatch.Elapsed)).ConfigureAwait(false); cancellationToken.ThrowIfCancellationRequested <TaskCanceledException>(); stopWatch.Stop(); // Process the response. AmqpError.ThrowIfErrorResponse(response, EventHubName); return(MessageConverter.CreatePartitionPropertiesFromResponse(response)); } 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, mark the exception as active and break out of the loop. ++failedAttemptCount; retryDelay = retryPolicy.CalculateRetryDelay(ex, failedAttemptCount); if ((retryDelay.HasValue) && (!ConnectionScope.IsDisposed) && (!cancellationToken.IsCancellationRequested)) { EventHubsEventSource.Log.GetPartitionPropertiesError(EventHubName, partitionId, ex.Message); await Task.Delay(retryDelay.Value, cancellationToken).ConfigureAwait(false); tryTimeout = retryPolicy.CalculateTryTimeout(failedAttemptCount); stopWatch.Reset(); } 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.GetPartitionPropertiesError(EventHubName, partitionId, ex.Message); throw; } finally { stopWatch.Stop(); EventHubsEventSource.Log.GetPartitionPropertiesComplete(EventHubName, partitionId); } }
/// <summary> /// Sends an AMQP message that contains a batch of events to the associated Event Hub. If the size of events exceed the /// maximum size of a single batch, an exception will be triggered and the send will fail. /// </summary> /// /// <param name="messageFactory">A factory which can be used to produce an AMQP message containing the batch of events to be sent.</param> /// <param name="partitionKey">The hashing key to use for influencing the partition to which events should be routed.</param> /// <param name="cancellationToken">An optional <see cref="CancellationToken"/> instance to signal the request to cancel the operation.</param> /// protected virtual async Task SendAsync(Func <AmqpMessage> messageFactory, string partitionKey, CancellationToken cancellationToken) { var failedAttemptCount = 0; var logPartition = PartitionId ?? partitionKey; var retryDelay = default(TimeSpan?); var messageHash = default(string); var stopWatch = Stopwatch.StartNew(); SendingAmqpLink link; try { while (!cancellationToken.IsCancellationRequested) { try { using AmqpMessage batchMessage = messageFactory(); messageHash = batchMessage.GetHashCode().ToString(); EventHubsEventSource.Log.EventPublishStart(EventHubName, logPartition, messageHash); link = await SendLink.GetOrCreateAsync(UseMinimum(ConnectionScope.SessionTimeout, _tryTimeout)).ConfigureAwait(false); cancellationToken.ThrowIfCancellationRequested <TaskCanceledException>(); // Validate that the batch of messages is not too large to send. This is done after the link is created to ensure // that the maximum message size is known, as it is dictated by the service using the link. if (batchMessage.SerializedMessageSize > MaximumMessageSize) { throw new MessageSizeExceededException(EventHubName, string.Format(Resources.MessageSizeExceeded, messageHash, batchMessage.SerializedMessageSize, MaximumMessageSize)); } // Attempt to send the message batch. var deliveryTag = new ArraySegment <byte>(BitConverter.GetBytes(Interlocked.Increment(ref _deliveryCount))); var outcome = await link.SendMessageAsync(batchMessage, deliveryTag, AmqpConstants.NullBinary, _tryTimeout.CalculateRemaining(stopWatch.Elapsed)).ConfigureAwait(false); cancellationToken.ThrowIfCancellationRequested <TaskCanceledException>(); if (outcome.DescriptorCode != Accepted.Code) { throw AmqpError.CreateExceptionForError((outcome as Rejected)?.Error, EventHubName); } // The send operation should be considered successful; return to // exit the retry loop. return; } 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.EventPublishError(EventHubName, logPartition, messageHash, ex.Message); await Task.Delay(retryDelay.Value, cancellationToken).ConfigureAwait(false); stopWatch.Reset(); } 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.EventPublishError(EventHubName, logPartition, messageHash, ex.Message); throw; } finally { stopWatch.Stop(); EventHubsEventSource.Log.EventPublishComplete(EventHubName, logPartition, messageHash); } }
/// <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); } }