public void Initialize(EntityInfoInternal entity, Func <IncomingMessageDetailsInternal, ReceiveContextInternal, Task> callback, Func <Exception, Task> errorCallback, Action <Exception> criticalError, Func <ErrorContext, Task <ErrorHandleResult> > processingFailureCallback, int maximumConcurrency)
        {
            incomingCallback               = callback;
            this.criticalError             = criticalError;
            this.errorCallback             = errorCallback ?? EmptyErrorCallback;
            this.processingFailureCallback = processingFailureCallback;
            this.entity = entity;

            fullPath = entity.Path;
            if (entity.Type == EntityType.Subscription)
            {
                var topic = entity.RelationShips.First(r => r.Type == EntityRelationShipTypeInternal.Subscription);
                fullPath = SubscriptionClient.FormatSubscriptionPath(topic.Target.Path, entity.Path);
            }

            wrapInScope = settings.TransportTransactionMode == TransportTransactionMode.SendsAtomicWithReceive;
            // batching will be applied for transaction modes other than SendsAtomicWithReceive
            completionCanBeBatched = !wrapInScope;

            var numberOfClients = settings.NumberOfClients;
            var concurrency     = maximumConcurrency / (double)numberOfClients;

            maxConcurrentCalls = concurrency > 1 ? (int)Math.Round(concurrency, MidpointRounding.AwayFromZero) : 1;
            if (Math.Abs(maxConcurrentCalls - concurrency) > 0)
            {
                logger.InfoFormat("The maximum concurrency on message receiver instance for '{0}' has been adjusted to '{1}', because the total maximum concurrency '{2}' wasn't divisable by the number of clients '{3}'", fullPath, maxConcurrentCalls, maximumConcurrency, numberOfClients);
            }

            internalReceivers = new IMessageReceiverInternal[numberOfClients];
            onMessageOptions  = new OnMessageOptions[numberOfClients];

            // when we don't batch we don't need the completion infrastructure
            completion = completionCanBeBatched ? new MultiProducerConcurrentCompletion <Guid>(1000, TimeSpan.FromSeconds(1), 6, numberOfClients) : null;
        }
        Task ReceiveMessage(IMessageReceiverInternal internalReceiver, BrokeredMessage message, int slotNumber, ConcurrentDictionary <Task, Task> pipelineInvocations)
        {
            var processTask = ProcessMessage(internalReceiver, message, slotNumber);

            pipelineInvocations.TryAdd(processTask, processTask);
            processTask.ContinueWith((t, state) =>
            {
                var invocations = (ConcurrentDictionary <Task, Task>)state;
                invocations.TryRemove(t, out var _);
            }, pipelineInvocations, TaskContinuationOptions.ExecuteSynchronously).Ignore();
            return(processTask);
        }
Esempio n. 3
0
        public static async Task <bool> SafeCompleteBatchAsync(this IMessageReceiverInternal messageReceiver, IEnumerable <Guid> lockTokens)
        {
            try
            {
                await messageReceiver.CompleteBatchAsync(lockTokens).ConfigureAwait(false);

                return(true);
            }
            catch (MessageLockLostException ex)
            {
                // It's too late to compensate the loss of a message lock. We should just ignore it so that it does not break the receive loop.
                Log.Warn($"A message lock lost exception occurred while trying to complete a batch of messages, you may consider to increase the lock duration or reduce the prefetch count, the exception was {ex.Message}", ex);
            }
            catch (MessagingException ex)
            {
                // There is nothing we can do as the connection may have been lost, or the underlying queue may have been removed.
                // If Complete() fails with this exception, the only recourse is to receive another message.
                Log.Warn($"A messaging exception occurred while trying to complete a batch of message, this might imply that the connection was lost or the underlying queue got removed, the exception was {ex.Message}", ex);
            }
            catch (ObjectDisposedException ex)
            {
                // There is nothing we can do as the object has already been disposed elsewhere
                Log.Warn($"An object disposed exception occurred while trying to complete a batch of messages, this might imply that the connection was lost or the underlying queue got removed, the exception was {ex.Message}", ex);
            }
            catch (TransactionException ex)
            {
                // ASB Sdk beat us to it
                Log.Warn($"A transaction exception occurred while trying to complete a batch of messages, this probably means that the Azure ServiceBus SDK has rolled back the transaction already, the exception was {ex.Message}", ex);
            }
            catch (TimeoutException ex)
            {
                // took to long
                Log.Warn($"A timeout exception occurred while trying to complete a batch of messages, the exception was {ex.Message}", ex);
            }
            return(false);
        }
        async Task ProcessMessage(IMessageReceiverInternal internalReceiver, BrokeredMessage message, int slotNumber)
        {
            if (stopping)
            {
                logger.Info($"Received message with ID {message.MessageId} while shutting down. Message will not be processed and will be retried after {message.LockedUntilUtc}.");
                return;
            }

            try
            {
                IncomingMessageDetailsInternal incomingMessage;
                try
                {
                    incomingMessage = brokeredMessageConverter.Convert(message);
                }
                catch (UnsupportedBrokeredMessageBodyTypeException exception)
                {
                    await message.DeadLetterAsync("BrokeredMessage to IncomingMessageDetails conversion failure", exception.ToString()).ConfigureAwait(false);

                    return;
                }

                var context = new BrokeredMessageReceiveContextInternal(message, entity, internalReceiver.Mode);
                try
                {
                    var scope = wrapInScope
                        ? new TransactionScope(TransactionScopeOption.RequiresNew, new TransactionOptions
                    {
                        IsolationLevel = IsolationLevel.Serializable
                    }, TransactionScopeAsyncFlowOption.Enabled)
                        : null;
                    {
                        using (scope)
                        {
                            await incomingCallback(incomingMessage, context).ConfigureAwait(false);

                            if (context.CancellationToken.IsCancellationRequested)
                            {
                                await AbandonOnCancellation(message).ConfigureAwait(false);
                            }
                            else
                            {
                                var wasCompleted = await HandleCompletion(message, context, completionCanBeBatched, slotNumber).ConfigureAwait(false);

                                if (wasCompleted)
                                {
                                    scope?.Complete();
                                }
                            }
                        }
                    }
                }
                catch (Exception exception) when(!stopping)
                {
                    // and go into recovery mode so that no new messages are added to the transfer queue
                    context.Recovering = true;

                    // pass the context into the error pipeline
                    var transportTransaction = new TransportTransaction();

                    transportTransaction.Set(context);
                    var errorContext = new ErrorContext(exception, incomingMessage.Headers, incomingMessage.MessageId, incomingMessage.Body, transportTransaction, message.DeliveryCount);

                    var result = await processingFailureCallback(errorContext).ConfigureAwait(false);

                    if (result == ErrorHandleResult.RetryRequired)
                    {
                        await Abandon(message, exception).ConfigureAwait(false);
                    }
                    else
                    {
                        await HandleCompletion(message, context, completionCanBeBatched, slotNumber).ConfigureAwait(false);
                    }
                }
            }
            catch (Exception onErrorException)
            {
                await Abandon(message, onErrorException).ConfigureAwait(false);
                await errorCallback(onErrorException).ConfigureAwait(false);
            }
        }