/// <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);
        }
Beispiel #2
0
        /// <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);
            }
        }
Beispiel #4
0
        /// <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);
            }
        }