internal async Task ProcessMessageAsync(ProcessMessageEventArgs args) { EnsureIsRunning(); _concurrencyUpdateManager?.MessageProcessed(); using (CancellationTokenSource linkedCts = CancellationTokenSource.CreateLinkedTokenSource(args.CancellationToken, _cancellationTokenSource.Token)) { var actions = new ServiceBusMessageActions(args); if (!await _messageProcessor.Value.BeginProcessingMessageAsync(actions, args.Message, linkedCts.Token).ConfigureAwait(false)) { return; } var receiveActions = new ServiceBusReceiveActions(args); ServiceBusTriggerInput input = ServiceBusTriggerInput.CreateSingle(args.Message, actions, receiveActions, _client.Value); TriggeredFunctionData data = input.GetTriggerFunctionData(); FunctionResult result = await _triggerExecutor.TryExecuteAsync(data, linkedCts.Token).ConfigureAwait(false); try { await _messageProcessor.Value.CompleteProcessingMessageAsync(actions, args.Message, result, linkedCts.Token) .ConfigureAwait(false); } finally { receiveActions.EndExecutionScope(); } } }
public static async Task Run( [ServiceBusTrigger("<queue_name>", Connection = "<connection_name>", IsSessionsEnabled = true)] ServiceBusReceivedMessage message, ServiceBusMessageActions messageActions, ServiceBusReceiveActions receiveActions) { if (message.MessageId == "1") { await messageActions.DeadLetterMessageAsync(message); } else { await messageActions.CompleteMessageAsync(message); // attempt to receive additional messages in this session await receiveActions.ReceiveMessagesAsync(maxMessages : 10); } }
private async Task RunBatchReceiveLoopAsync(CancellationToken cancellationToken) { ServiceBusClient sessionClient = null; ServiceBusReceiver receiver = null; if (_isSessionsEnabled) { sessionClient = _client.Value; } else { receiver = _batchReceiver.Value; } // The batch receive loop below only executes functions at a concurrency level of 1, // so we don't need to do anything special when DynamicConcurrency is enabled. If in // the future we make this loop concurrent, we'll have to check with ConcurrencyManager. while (true) { try { if (cancellationToken.IsCancellationRequested) { _logger.LogInformation($"Message processing has been stopped or cancelled ({_details.Value})"); return; } if (_isSessionsEnabled && (receiver == null || receiver.IsClosed)) { try { receiver = await sessionClient.AcceptNextSessionAsync( _entityPath, new ServiceBusSessionReceiverOptions { PrefetchCount = _serviceBusOptions.PrefetchCount }, cancellationToken).ConfigureAwait(false); } catch (ServiceBusException ex) when(ex.Reason == ServiceBusFailureReason.ServiceTimeout) { // it's expected if the entity is empty, try next time continue; } } IReadOnlyList <ServiceBusReceivedMessage> messages = await receiver.ReceiveMessagesAsync( _serviceBusOptions.MaxMessageBatchSize, cancellationToken : cancellationToken).AwaitWithCancellation(cancellationToken); if (messages.Count > 0) { var messageActions = _isSessionsEnabled ? new ServiceBusSessionMessageActions((ServiceBusSessionReceiver)receiver) : new ServiceBusMessageActions(receiver); var receiveActions = new ServiceBusReceiveActions(receiver); ServiceBusReceivedMessage[] messagesArray = messages.ToArray(); ServiceBusTriggerInput input = ServiceBusTriggerInput.CreateBatch( messagesArray, messageActions, receiveActions, _client.Value); FunctionResult result = await _triggerExecutor.TryExecuteAsync(input.GetTriggerFunctionData(), cancellationToken).ConfigureAwait(false); receiveActions.EndExecutionScope(); var processedMessages = messagesArray.Concat(receiveActions.Messages.Keys); // Complete batch of messages only if the execution was successful if (_autoCompleteMessages && result.Succeeded) { List <Task> completeTasks = new List <Task>(); foreach (ServiceBusReceivedMessage message in processedMessages) { // skip messages that were settled in the user's function if (input.MessageActions.SettledMessages.ContainsKey(message)) { continue; } // Pass CancellationToken.None to allow autocompletion to finish even when shutting down completeTasks.Add(receiver.CompleteMessageAsync(message, CancellationToken.None)); } await Task.WhenAll(completeTasks).ConfigureAwait(false); } else if (!result.Succeeded) { // For failed executions, we abandon the messages regardless of the autoCompleteMessages configuration. // This matches the behavior that happens for single dispatch functions as the processor does the same thing // in the Service Bus SDK. List <Task> abandonTasks = new List <Task>(); foreach (ServiceBusReceivedMessage message in processedMessages) { // skip messages that were settled in the user's function if (input.MessageActions.SettledMessages.ContainsKey(message)) { continue; } // Pass CancellationToken.None to allow abandon to finish even when shutting down abandonTasks.Add(receiver.AbandonMessageAsync(message, cancellationToken: CancellationToken.None)); } await Task.WhenAll(abandonTasks).ConfigureAwait(false); } if (_isSessionsEnabled) { if (((ServiceBusSessionMessageActions)messageActions).ShouldReleaseSession) { // Use CancellationToken.None to attempt to close the receiver even when shutting down await receiver.CloseAsync(CancellationToken.None).ConfigureAwait(false); } } } else { // Close the session and release the session lock after draining all messages for the accepted session. if (_isSessionsEnabled) { // Use CancellationToken.None to attempt to close the receiver even when shutting down await receiver.CloseAsync(CancellationToken.None).ConfigureAwait(false); } } } catch (ObjectDisposedException) { // Ignore as we are stopping the host } catch (OperationCanceledException) when(cancellationToken.IsCancellationRequested) { // Ignore as we are stopping the host _logger.LogInformation($"Message processing has been stopped or cancelled ({_details.Value})"); } catch (Exception ex) { // Log another exception _logger.LogError(ex, $"An unhandled exception occurred in the message batch receive loop ({_details.Value})"); if (_isSessionsEnabled && receiver != null) { // Attempt to close the session and release session lock to accept a new session on the next loop iteration try { // Use CancellationToken.None to attempt to close the receiver even when shutting down await receiver.CloseAsync(CancellationToken.None).ConfigureAwait(false); } catch { // Best effort receiver = null; } } } } }