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); }
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); } }