Task DispatchBatches(IList <BatchInternal> outgoingBatches, BrokeredMessageReceiveContextInternal receiveContext) { // received brokered message has already been completed, so send everything out immediately if (receiveContext.ReceiveMode == ReceiveMode.ReceiveAndDelete) { return(routeOutgoingBatches.RouteBatches(outgoingBatches, receiveContext, DispatchConsistency.Default)); } // default behavior is to postpone sends until complete (and potentially complete them as a single tx if possible) // but some messages may need to go out immediately return(DispatchAccordingToIsolationLevel(outgoingBatches, receiveContext)); }
Task <bool> HandleCompletion(BrokeredMessage message, BrokeredMessageReceiveContextInternal context, bool canBeBatched, int slotNumber) { if (settings.ReceiveMode == ReceiveMode.PeekLock) { if (canBeBatched) { completion.Push(message.LockToken, slotNumber); } else { return(context.IncomingBrokeredMessage.SafeCompleteAsync()); } } return(TaskEx.CompletedTrue); }
internal async Task RouteBatch(BatchInternal batch, BrokeredMessageReceiveContextInternal context, DispatchConsistency consistency) { var outgoingBatches = batch.Operations; var passiveNamespaces = batch.Destinations.Namespaces.Where(n => n.Mode == NamespaceMode.Passive).ToList(); var pendingSends = new List <Task>(); foreach (var entity in batch.Destinations.Entities.Where(entity => entity.Namespace.Mode == NamespaceMode.Active)) { var routingOptions = GetRoutingOptions(context, consistency); if (routingOptions.SendVia && !string.IsNullOrEmpty(routingOptions.ViaEntityPath)) { logger.DebugFormat("Routing {0} messages to {1} via {2}", outgoingBatches.Count, entity.Path, routingOptions.ViaEntityPath); } else { logger.DebugFormat("Routing {0} messages to {1}", outgoingBatches.Count, entity.Path); } // don't use via on fallback, not supported across namespaces var fallbacks = new List <IMessageSenderInternal>(passiveNamespaces.Count); foreach (var passiveNamespace in passiveNamespaces) { fallbacks.Add(await sendersLifeCycleManager.Get(entity.Path, null, passiveNamespace.Alias) .ConfigureAwait(false)); } var ns = entity.Namespace; // only use via if the destination and via namespace are the same var via = routingOptions.SendVia && ns.ConnectionString == routingOptions.ViaConnectionString ? routingOptions.ViaEntityPath : null; var suppressTransaction = via == null; var messageSender = await sendersLifeCycleManager.Get(entity.Path, via, ns.Alias) .ConfigureAwait(false); routingOptions.DestinationEntityPath = entity.Path; routingOptions.DestinationNamespace = ns; var brokeredMessages = outgoingMessageConverter.Convert(outgoingBatches, routingOptions).ToList(); pendingSends.Add(RouteOutBatchesWithFallbackAndLogExceptionsAsync(messageSender, fallbacks, brokeredMessages, suppressTransaction)); } await Task.WhenAll(pendingSends) .ConfigureAwait(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); } }
async Task DispatchWithTransactionScopeIfRequired(IList <BatchInternal> toBeDispatchedOnComplete, BrokeredMessageReceiveContextInternal context, DispatchConsistency consistency) { if (context.CancellationToken.IsCancellationRequested || !toBeDispatchedOnComplete.Any()) { return; } await routeOutgoingBatches.RouteBatches(toBeDispatchedOnComplete, context, consistency).ConfigureAwait(false); }
async Task DispatchAccordingToIsolationLevel(IList <BatchInternal> outgoingBatches, BrokeredMessageReceiveContextInternal receiveContext) { var batchesWithIsolatedDispatchConsistency = outgoingBatches.Where(t => t.RequiredDispatchConsistency == DispatchConsistency.Isolated); var batchesWithDefaultConsistency = outgoingBatches.Where(t => t.RequiredDispatchConsistency == DispatchConsistency.Default).ToList(); await routeOutgoingBatches.RouteBatches(batchesWithIsolatedDispatchConsistency, receiveContext, DispatchConsistency.Isolated).ConfigureAwait(false); await DispatchWithTransactionScopeIfRequired(batchesWithDefaultConsistency, receiveContext, DispatchConsistency.Default).ConfigureAwait(false); }