public async Task <T> RunOperation <T>(Func <Task <T> > operation)
        {
            var failedAttemptCount = 0;

            var stopWatch = Stopwatch.StartNew();

            try
            {
                TimeSpan tryTimeout = RetryPolicy.CalculateTryTimeout(0);

                while (!CancellationToken.IsCancellationRequested)
                {
                    try
                    {
                        return(await operation().ConfigureAwait(false));
                    }

                    catch (Exception ex)
                    {
                        Exception activeEx = ex.TranslateServiceException(EntityName);

                        // 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;
                        TimeSpan?retryDelay = RetryPolicy.CalculateRetryDelay(ex, failedAttemptCount);
                        if (retryDelay.HasValue && !Scope.IsDisposed && !CancellationToken.IsCancellationRequested)
                        {
                            //EventHubsEventSource.Log.GetPropertiesError(EventHubName, activeEx.Message);
                            await Task.Delay(retryDelay.Value, CancellationToken).ConfigureAwait(false);

                            tryTimeout = RetryPolicy.CalculateTryTimeout(failedAttemptCount);
                            stopWatch.Reset();
                        }
                        else if (ex is AmqpException)
                        {
                            throw activeEx;
                        }
                        else
                        {
                            throw;
                        }
                    }
                }
                // If no value has been returned nor exception thrown by this point,
                // then cancellation has been requested.
                throw new TaskCanceledException();
            }
            catch (Exception exception)
            {
                throw exception;
                //TODO through correct exception throw AmqpExceptionHelper.GetClientException(exception);
            }
            finally
            {
                stopWatch.Stop();
                //TODO log correct completion event ServiceBusEventSource.Log.PeekMessagesComplete(EntityName);
            }
        }
Ejemplo n.º 2
0
        /// <summary>
        ///   Reads the set of partition publishing properties active for this producer at the time it was initialized.
        /// </summary>
        ///
        /// <param name="cancellationToken">The cancellation token to consider when creating the link.</param>
        ///
        /// <returns>The set of <see cref="PartitionPublishingProperties" /> observed when the producer was initialized.</returns>
        ///
        /// <remarks>
        ///   It is important to note that these properties are a snapshot of the service state at the time when the
        ///   producer was initialized; they do not necessarily represent the current state of the service.
        /// </remarks>
        ///
        public override async ValueTask <PartitionPublishingProperties> ReadInitializationPublishingPropertiesAsync(CancellationToken cancellationToken)
        {
            Argument.AssertNotClosed(_closed, nameof(AmqpProducer));
            Argument.AssertNotClosed(ConnectionScope.IsDisposed, nameof(EventHubConnection));

            // If the properties were already initialized, use them.

            if (InitializedPartitionProperties != null)
            {
                return(InitializedPartitionProperties);
            }

            // Initialize the properties by forcing the link to be opened.

            var failedAttemptCount = 0;
            var tryTimeout         = RetryPolicy.CalculateTryTimeout(0);

            while ((!cancellationToken.IsCancellationRequested) && (!_closed))
            {
                try
                {
                    if (!SendLink.TryGetOpenedObject(out _))
                    {
                        await SendLink.GetOrCreateAsync(UseMinimum(ConnectionScope.SessionTimeout, tryTimeout), cancellationToken).ConfigureAwait(false);
                    }

                    break;
                }
                catch (Exception ex)
                {
                    ++failedAttemptCount;

                    // 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.

                    var activeEx   = ex.TranslateServiceException(EventHubName);
                    var retryDelay = RetryPolicy.CalculateRetryDelay(activeEx, failedAttemptCount);

                    if ((retryDelay.HasValue) && (!ConnectionScope.IsDisposed) && (!cancellationToken.IsCancellationRequested))
                    {
                        await Task.Delay(retryDelay.Value, cancellationToken).ConfigureAwait(false);

                        tryTimeout = RetryPolicy.CalculateTryTimeout(failedAttemptCount);
                    }
                    else if (ex is AmqpException)
                    {
                        ExceptionDispatchInfo.Capture(activeEx).Throw();
                    }
                    else
                    {
                        throw;
                    }
                }
            }

            cancellationToken.ThrowIfCancellationRequested <TaskCanceledException>();
            return(InitializedPartitionProperties);
        }
        /// <summary>
        ///   Applies the checkpoint store's <see cref="RetryPolicy" /> to a specified function.
        /// </summary>
        ///
        /// <param name="functionToRetry">The function to which the retry policy should be applied.</param>
        /// <param name="cancellationToken">A <see cref="CancellationToken" /> instance to signal the request to cancel the operation.</param>
        ///
        /// <returns>The value returned by the function to which the retry policy has been applied.</returns>
        ///
        private async Task ApplyRetryPolicy(Func <CancellationToken, Task> functionToRetry,
                                            CancellationToken cancellationToken)
        {
            TimeSpan?retryDelay;

            var failedAttemptCount = 0;
            var tryTimeout         = RetryPolicy.CalculateTryTimeout(0);
            var timeoutTokenSource = default(CancellationTokenSource);
            var linkedTokenSource  = default(CancellationTokenSource);

            while (!cancellationToken.IsCancellationRequested)
            {
                try
                {
                    timeoutTokenSource = new CancellationTokenSource(tryTimeout);
                    linkedTokenSource  = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, timeoutTokenSource.Token);

                    await functionToRetry(linkedTokenSource.Token).ConfigureAwait(false);

                    return;
                }
                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) && (!cancellationToken.IsCancellationRequested))
                    {
                        await Task.Delay(retryDelay.Value, cancellationToken).ConfigureAwait(false);

                        tryTimeout = RetryPolicy.CalculateTryTimeout(failedAttemptCount);
                    }
                    else
                    {
                        timeoutTokenSource?.Token.ThrowIfCancellationRequested <TimeoutException>();
                        throw;
                    }
                }
                finally
                {
                    timeoutTokenSource?.Dispose();
                    linkedTokenSource?.Dispose();
                }
            }

            // If no value has been returned nor exception thrown by this point,
            // then cancellation has been requested.

            throw new TaskCanceledException();
        }
        /// <summary>
        ///   Creates the AMQP link to be used for producer-related operations and ensures
        ///   that the corresponding state for the producer has been updated based on the link
        ///   configuration.
        /// </summary>
        ///
        /// <param name="partitionId">The identifier of the Event Hub partition to which it is bound; if unbound, <c>null</c>.</param>
        /// <param name="producerIdentifier">The identifier associated with the producer.</param>
        /// <param name="partitionOptions">The set of options, if any, that should be considered when initializing the producer.</param>
        /// <param name="timeout">The timeout to apply for creating the link.</param>
        /// <param name="cancellationToken">The cancellation token to consider when creating the link.</param>
        ///
        /// <returns>The AMQP link to use for producer-related operations.</returns>
        ///
        /// <remarks>
        ///   This method will modify class-level state, setting those attributes that depend on the AMQP
        ///   link configuration.  There exists a benign race condition in doing so, as there may be multiple
        ///   concurrent callers.  In this case, the attributes may be set multiple times but the resulting
        ///   value will be the same.
        /// </remarks>
        ///
        protected virtual async Task <SendingAmqpLink> CreateLinkAndEnsureProducerStateAsync(string partitionId,
                                                                                             string producerIdentifier,
                                                                                             PartitionPublishingOptionsInternal partitionOptions,
                                                                                             TimeSpan timeout,
                                                                                             CancellationToken cancellationToken)
        {
            var link             = default(SendingAmqpLink);
            var operationTimeout = RetryPolicy.CalculateTryTimeout(0);

            try
            {
                link = await ConnectionScope.OpenProducerLinkAsync(partitionId, ActiveFeatures, partitionOptions, operationTimeout, timeout, producerIdentifier, cancellationToken).ConfigureAwait(false);

                if (!MaximumMessageSize.HasValue)
                {
                    // This delay is necessary to prevent the link from causing issues for subsequent
                    // operations after creating a batch.  Without it, operations using the link consistently
                    // timeout.  The length of the delay does not appear significant, just the act of introducing
                    // an asynchronous delay.
                    //
                    // For consistency the value used by the legacy Event Hubs client has been brought forward and
                    // used here.

                    await Task.Delay(15, cancellationToken).ConfigureAwait(false);

                    MaximumMessageSize = (long)link.Settings.MaxMessageSize;
                }

                if (InitializedPartitionProperties == null)
                {
                    var producerGroup = link.ExtractSettingPropertyValueOrDefault(AmqpProperty.ProducerGroupId, default(long?));
                    var ownerLevel    = link.ExtractSettingPropertyValueOrDefault(AmqpProperty.ProducerOwnerLevel, default(short?));
                    var sequence      = link.ExtractSettingPropertyValueOrDefault(AmqpProperty.ProducerSequenceNumber, default(int?));

                    // Once the properties are initialized, clear the starting sequence number to ensure that the current
                    // sequence tracked by the service is used should the link need to be recreated; this avoids the need for
                    // the transport producer to have awareness of the sequence numbers of events being sent.

                    InitializedPartitionProperties          = new PartitionPublishingPropertiesInternal(false, producerGroup, ownerLevel, sequence);
                    partitionOptions.StartingSequenceNumber = null;
                }
            }
            catch (Exception ex)
            {
                ExceptionDispatchInfo.Capture(ex.TranslateConnectionCloseDuringLinkCreationException(EventHubName)).Throw();
            }

            return(link);
        }
Ejemplo n.º 5
0
        /// <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);
                        cancellationToken.ThrowIfCancellationRequested <TaskCanceledException>();

                        link = await ReceiveLink.GetOrCreateAsync(UseMinimum(ConnectionScope.SessionTimeout, tryTimeout)).ConfigureAwait(false);

                        cancellationToken.ThrowIfCancellationRequested <TaskCanceledException>();

                        var messagesReceived = await Task.Factory.FromAsync <(ReceivingAmqpLink, int, TimeSpan), IEnumerable <AmqpMessage> >
                                               (
                            static (arguments, callback, state) =>
                        {
                            var(link, maximumEventCount, waitTime) = arguments;
                            return(link.BeginReceiveMessages(maximumEventCount, waitTime, callback, link));
                        },
Ejemplo n.º 6
0
        /// <summary>
        ///   Creates a size-constraint batch to which <see cref="EventData" /> may be added using a try-based pattern.  If an event would
        ///   exceed the maximum allowable size of the batch, the batch will not allow adding the event and signal that scenario using its
        ///   return value.
        ///
        ///   Because events that would violate the size constraint cannot be added, publishing a batch will not trigger an exception when
        ///   attempting to send the events to the Event Hubs service.
        /// </summary>
        ///
        /// <param name="options">The set of options to consider when creating this batch.</param>
        /// <param name="cancellationToken">An optional <see cref="CancellationToken"/> instance to signal the request to cancel the operation.</param>
        ///
        /// <returns>An <see cref="EventDataBatch" /> with the requested <paramref name="options"/>.</returns>
        ///
        public override async ValueTask <TransportEventBatch> CreateBatchAsync(CreateBatchOptions options,
                                                                               CancellationToken cancellationToken)
        {
            Argument.AssertNotNull(options, nameof(options));

            // Ensure that maximum message size has been determined; this depends on the underlying
            // AMQP link, so if not set, requesting the link will ensure that it is populated.

            if (!MaximumMessageSize.HasValue)
            {
                await SendLink.GetOrCreateAsync(RetryPolicy.CalculateTryTimeout(0)).ConfigureAwait(false);
            }

            // Ensure that there was a maximum size populated; if none was provided,
            // default to the maximum size allowed by the link.

            options.MaximumSizeInBytes ??= MaximumMessageSize;

            Argument.AssertInRange(options.MaximumSizeInBytes.Value, EventHubProducerClient.MinimumBatchSizeLimit, MaximumMessageSize.Value, nameof(options.MaximumSizeInBytes));
            return(new AmqpEventBatch(MessageConverter, options));
        }
Ejemplo n.º 7
0
        /// <summary>
        ///   Creates a <see cref="PartitionReceiver" /> which allows events from a specific partition of an Event Hub to be read
        ///   with a greater level of control over communication with the Event Hubs service than is offered by other ways of consuming
        ///   events.
        /// </summary>
        ///
        /// <param name="partitionId">The identifier of the Event Hub partition from which events will be received.</param>
        /// <param name="startingPosition">The position within the partition where the consumer should begin reading events.</param>
        ///
        /// <returns>A <see cref="PartitionReceiver"/> to be used for iterating over events in the partition.</returns>
        ///
        /// <remarks>
        ///   It is recommended that the <see cref="EventProcessorClient" /> or <see cref="EventHubConsumerClient" />
        ///   be used for reading and processing events for the majority of scenarios.  The partition receiver is
        ///   intended to enable scenarios with special needs which require more direct control.
        /// </remarks>
        ///
        /// <seealso cref="EventProcessorClient" />
        /// <seealso cref="ReadEventsFromPartitionAsync(string, EventPosition, CancellationToken)"/>
        /// <seealso cref="ReadEventsFromPartitionAsync(string, EventPosition, TimeSpan?, CancellationToken)"/>
        ///
        public virtual PartitionReceiver CreatePartitionReceiver(string partitionId,
                                                                 EventPosition startingPosition)
        {
            var transportConsumer = Connection.CreateTransportConsumer(ConsumerGroup, partitionId, startingPosition, Options);

            if (!ActiveConsumers.TryAdd(Guid.NewGuid().ToString(), transportConsumer))
            {
                transportConsumer.CloseAsync(CancellationToken.None).GetAwaiter().GetResult();
                throw new EventHubsException(false, EventHubName, String.Format(CultureInfo.CurrentCulture, Resources.FailedToCreateReader, EventHubName, partitionId, ConsumerGroup));
            }

            return(new PartitionReceiver
                   (
                       ConsumerGroup,
                       partitionId,
                       EventHubName,
                       Options.TrackLastEnqueuedEventInformation,
                       Options.DefaultMaximumReceiveWaitTime ?? RetryPolicy.CalculateTryTimeout(0),
                       transportConsumer
                   ));
        }
Ejemplo n.º 8
0
        /// <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
            {
                var tryTimeout = RetryPolicy.CalculateTryTimeout(0);

                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 EventHubsException(EventHubName, string.Format(Resources.MessageSizeExceeded, messageHash, batchMessage.SerializedMessageSize, MaximumMessageSize), EventHubsException.FailureReason.MessageSizeExceeded);
                        }

                        // 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 (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.EventPublishError(EventHubName, logPartition, messageHash, activeEx.Message);
                            await Task.Delay(retryDelay.Value, cancellationToken).ConfigureAwait(false);

                            tryTimeout = RetryPolicy.CalculateTryTimeout(failedAttemptCount);
                            stopWatch.Reset();
                        }
                        else if (ex is AmqpException)
                        {
                            throw activeEx;
                        }
                        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);
            }
        }
Ejemplo n.º 9
0
        /// <summary>
        ///   Creates a size-constraint batch to which <see cref="EventData" /> may be added using a try-based pattern.  If an event would
        ///   exceed the maximum allowable size of the batch, the batch will not allow adding the event and signal that scenario using its
        ///   return value.
        ///
        ///   Because events that would violate the size constraint cannot be added, publishing a batch will not trigger an exception when
        ///   attempting to send the events to the Event Hubs service.
        /// </summary>
        ///
        /// <param name="options">The set of options to consider when creating this batch.</param>
        /// <param name="cancellationToken">An optional <see cref="CancellationToken"/> instance to signal the request to cancel the operation.</param>
        ///
        /// <returns>An <see cref="EventDataBatch" /> with the requested <paramref name="options"/>.</returns>
        ///
        public override async ValueTask <TransportEventBatch> CreateBatchAsync(CreateBatchOptions options,
                                                                               CancellationToken cancellationToken)
        {
            Argument.AssertNotNull(options, nameof(options));
            Argument.AssertNotClosed(_closed, nameof(AmqpProducer));

            cancellationToken.ThrowIfCancellationRequested <TaskCanceledException>();

            // Ensure that maximum message size has been determined; this depends on the underlying
            // AMQP link, so if not set, requesting the link will ensure that it is populated.

            if (!MaximumMessageSize.HasValue)
            {
                var failedAttemptCount = 0;
                var retryDelay         = default(TimeSpan?);
                var tryTimeout         = RetryPolicy.CalculateTryTimeout(0);

                while (!cancellationToken.IsCancellationRequested)
                {
                    try
                    {
                        await SendLink.GetOrCreateAsync(UseMinimum(ConnectionScope.SessionTimeout, tryTimeout)).ConfigureAwait(false);

                        break;
                    }
                    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))
                        {
                            await Task.Delay(retryDelay.Value, cancellationToken).ConfigureAwait(false);

                            tryTimeout = RetryPolicy.CalculateTryTimeout(failedAttemptCount);
                        }
                        else if (ex is AmqpException)
                        {
                            throw activeEx;
                        }
                        else
                        {
                            throw;
                        }
                    }
                }

                // If MaximumMessageSize has not been populated nor exception thrown
                // by this point, then cancellation has been requested.

                if (!MaximumMessageSize.HasValue)
                {
                    throw new TaskCanceledException();
                }
            }

            // Ensure that there was a maximum size populated; if none was provided,
            // default to the maximum size allowed by the link.

            options.MaximumSizeInBytes ??= MaximumMessageSize;

            Argument.AssertInRange(options.MaximumSizeInBytes.Value, EventHubProducerClient.MinimumBatchSizeLimit, MaximumMessageSize.Value, nameof(options.MaximumSizeInBytes));
            return(new AmqpEventBatch(MessageConverter, options));
        }
Ejemplo n.º 10
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 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);
            }
        }
Ejemplo n.º 11
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 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 override async Task <IEnumerable <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 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 ((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);

                            tryTimeout = RetryPolicy.CalculateTryTimeout(failedAttemptCount);
                        }
                        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);
            }
        }
Ejemplo n.º 12
0
        /// <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="messages">The set of AMQP messages to packaged in a batch envelope and 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>
        ///
        /// <remarks>
        ///   Callers retain ownership of the <paramref name="messages" /> passed and hold responsibility for
        ///   ensuring that they are disposed.
        /// </remarks>
        ///
        protected virtual async Task SendAsync(IReadOnlyCollection <AmqpMessage> messages,
                                               string partitionKey,
                                               CancellationToken cancellationToken)
        {
            var failedAttemptCount = 0;
            var logPartition       = PartitionId ?? partitionKey;
            var operationId        = Guid.NewGuid().ToString("D", CultureInfo.InvariantCulture);

            TimeSpan?       retryDelay;
            SendingAmqpLink link;

            try
            {
                var tryTimeout = RetryPolicy.CalculateTryTimeout(0);

                while (!cancellationToken.IsCancellationRequested)
                {
                    EventHubsEventSource.Log.EventPublishStart(EventHubName, logPartition, operationId);

                    try
                    {
                        using AmqpMessage batchMessage = MessageConverter.CreateBatchFromMessages(messages);

                        if (!SendLink.TryGetOpenedObject(out link))
                        {
                            link = await SendLink.GetOrCreateAsync(UseMinimum(ConnectionScope.SessionTimeout, tryTimeout), cancellationToken).ConfigureAwait(false);
                        }

                        // 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 EventHubsException(EventHubName, string.Format(CultureInfo.CurrentCulture, Resources.MessageSizeExceeded, operationId, batchMessage.SerializedMessageSize, MaximumMessageSize), EventHubsException.FailureReason.MessageSizeExceeded);
                        }

                        // 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, cancellationToken).ConfigureAwait(false);

                        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 (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) && (!_closed) && (!cancellationToken.IsCancellationRequested))
                        {
                            EventHubsEventSource.Log.EventPublishError(EventHubName, logPartition, operationId, activeEx.Message);
                            await Task.Delay(retryDelay.Value, 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.EventPublishError(EventHubName, logPartition, operationId, ex.Message);
                throw;
            }
            finally
            {
                EventHubsEventSource.Log.EventPublishComplete(EventHubName, logPartition, operationId, failedAttemptCount);
            }
        }
Ejemplo n.º 13
0
        /// <summary>
        ///   Creates the AMQP link to be used for consumer-related operations.
        /// </summary>
        ///
        /// <param name="consumerGroup">The consumer group of the Event Hub to which the link is bound.</param>
        /// <param name="partitionId">The identifier of the Event Hub partition to which the link is bound.</param>
        /// <param name="consumerIdentifier">The identifier associated with the consumer.</param>
        /// <param name="eventStartingPosition">The place within the partition's event stream to begin consuming events.</param>
        /// <param name="prefetchCount">Controls the number of events received and queued locally without regard to whether an operation was requested.</param>
        /// <param name="prefetchSizeInBytes">The cache size of the prefetch queue. When set, the link makes a best effort to ensure prefetched messages fit into the specified size.</param>
        /// <param name="ownerLevel">The relative priority to associate with the link; for a non-exclusive link, this value should be <c>null</c>.</param>
        /// <param name="trackLastEnqueuedEventProperties">Indicates whether information on the last enqueued event on the partition is sent as events are received.</param>
        /// <param name="timeout">The timeout to apply for creating the link.</param>
        /// <param name="cancellationToken">The cancellation token to consider when creating the link.</param>
        ///
        /// <returns>The AMQP link to use for consumer-related operations.</returns>
        ///
        protected async Task <ReceivingAmqpLink> CreateConsumerLinkAsync(string consumerGroup,
                                                                         string partitionId,
                                                                         string consumerIdentifier,
                                                                         EventPosition eventStartingPosition,
                                                                         uint prefetchCount,
                                                                         long?prefetchSizeInBytes,
                                                                         long?ownerLevel,
                                                                         bool trackLastEnqueuedEventProperties,
                                                                         TimeSpan timeout,
                                                                         CancellationToken cancellationToken)
        {
            // Determine if there is an active is an active exception that needs to be surfaced,
            // do so now.
            //
            // Note that this is a benign race; it is possible that multiple threads
            // will observe the active exception and surface it, even if the consumer is not
            // considered invalid.  This is reasonable behavior for this scenario as the underlying
            // condition is itself a race condition between multiple consumers.

            var activeException = _activePartitionStolenException;

            if (activeException != null)
            {
                // If the consumer should not be considered invalid, clear the active exception if
                // it hasn't already been reset.

                if (!InvalidateConsumerWhenPartitionStolen)
                {
                    Interlocked.CompareExchange(ref _activePartitionStolenException, null, activeException);
                }

                EventHubsEventSource.Log.AmqpConsumerLinkCreateCapturedErrorThrow(EventHubName, consumerGroup, partitionId, ownerLevel?.ToString(CultureInfo.InvariantCulture), eventStartingPosition.ToString(), activeException.Message);
                ExceptionDispatchInfo.Capture(activeException).Throw();
            }

            // Create and open the consumer link.

            var link       = default(ReceivingAmqpLink);
            var tryTimeout = RetryPolicy.CalculateTryTimeout(0);

            try
            {
                link = await ConnectionScope.OpenConsumerLinkAsync(
                    consumerGroup,
                    partitionId,
                    eventStartingPosition,
                    tryTimeout,
                    timeout,
                    prefetchCount,
                    prefetchSizeInBytes,
                    ownerLevel,
                    trackLastEnqueuedEventProperties,
                    consumerIdentifier,
                    cancellationToken).ConfigureAwait(false);
            }
            catch (Exception ex)
            {
                ExceptionDispatchInfo.Capture(ex.TranslateConnectionCloseDuringLinkCreationException(EventHubName)).Throw();
            }

            return(link);
        }
Ejemplo n.º 14
0
        /// <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);
            }
        }