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 void GetBindingData_SingleDispatch_ReturnsExpectedValue() { IDictionary <string, object> userProps = new Dictionary <string, object>(); userProps.Add(new KeyValuePair <string, object>("prop1", "value1")); userProps.Add(new KeyValuePair <string, object>("prop2", "value2")); var message = CreateMessageWithSystemProperties(applicationProperties: userProps); var input = ServiceBusTriggerInput.CreateSingle(message); var strategy = new ServiceBusTriggerBindingStrategy(); var bindingData = strategy.GetBindingData(input); Assert.AreEqual(15, bindingData.Count); Assert.AreSame(input.Receiver, bindingData["MessageReceiver"]); Assert.AreSame(input.SessionReceiver, bindingData["MessageSession"]); Assert.AreEqual(message.LockToken, bindingData["LockToken"]); Assert.AreEqual(message.SequenceNumber, bindingData["SequenceNumber"]); Assert.AreEqual(message.DeliveryCount, bindingData["DeliveryCount"]); Assert.AreSame(message.DeadLetterSource, bindingData["DeadLetterSource"]); Assert.AreEqual(message.ExpiresAt, bindingData["ExpiresAtUtc"]); Assert.AreEqual(message.EnqueuedTime, bindingData["EnqueuedTimeUtc"]); Assert.AreSame(message.MessageId, bindingData["MessageId"]); Assert.AreSame(message.ContentType, bindingData["ContentType"]); Assert.AreSame(message.ReplyTo, bindingData["ReplyTo"]); Assert.AreSame(message.To, bindingData["To"]); Assert.AreSame(message.Subject, bindingData["Label"]); Assert.AreSame(message.CorrelationId, bindingData["CorrelationId"]); IDictionary <string, object> bindingDataUserProps = bindingData["ApplicationProperties"] as IDictionary <string, object>; Assert.NotNull(bindingDataUserProps); Assert.AreEqual("value1", bindingDataUserProps["prop1"]); Assert.AreEqual("value2", bindingDataUserProps["prop2"]); }
public void GetBindingData_MultipleDispatch_ReturnsExpectedValue() { var messages = new ServiceBusReceivedMessage[3] { CreateMessageWithSystemProperties("Event 1"), CreateMessageWithSystemProperties("Event 2"), CreateMessageWithSystemProperties("Event 3"), }; var input = ServiceBusTriggerInput.CreateBatch(messages, null, null); var strategy = new ServiceBusTriggerBindingStrategy(); var bindingData = strategy.GetBindingData(input); Assert.AreEqual(BindingContractCount, bindingData.Count); Assert.AreSame(input.MessageActions, bindingData["MessageReceiver"]); Assert.AreSame(input.MessageActions, bindingData["MessageSession"]); Assert.AreSame(input.MessageActions, bindingData["MessageActions"]); Assert.AreSame(input.MessageActions, bindingData["SessionActions"]); // verify an array was created for each binding data type Assert.AreEqual(messages.Length, ((int[])bindingData["DeliveryCountArray"]).Length); Assert.AreEqual(messages.Length, ((string[])bindingData["DeadLetterSourceArray"]).Length); Assert.AreEqual(messages.Length, ((string[])bindingData["LockTokenArray"]).Length); Assert.AreEqual(messages.Length, ((DateTime[])bindingData["ExpiresAtUtcArray"]).Length); Assert.AreEqual(messages.Length, ((DateTime[])bindingData["EnqueuedTimeUtcArray"]).Length); Assert.AreEqual(messages.Length, ((string[])bindingData["MessageIdArray"]).Length); Assert.AreEqual(messages.Length, ((string[])bindingData["ContentTypeArray"]).Length); Assert.AreEqual(messages.Length, ((string[])bindingData["ReplyToArray"]).Length); Assert.AreEqual(messages.Length, ((long[])bindingData["SequenceNumberArray"]).Length); Assert.AreEqual(messages.Length, ((string[])bindingData["ToArray"]).Length); Assert.AreEqual(messages.Length, ((string[])bindingData["SubjectArray"]).Length); Assert.AreEqual(messages.Length, ((string[])bindingData["CorrelationIdArray"]).Length); Assert.AreEqual(messages.Length, ((IDictionary <string, object>[])bindingData["ApplicationPropertiesArray"]).Length); }
internal async Task ProcessSessionMessageAsync(ProcessSessionMessageEventArgs args) { _concurrencyUpdateManager?.MessageProcessed(); using (CancellationTokenSource linkedCts = CancellationTokenSource.CreateLinkedTokenSource(args.CancellationToken, _cancellationTokenSource.Token)) { var actions = new ServiceBusSessionMessageActions(args); if (!await _sessionMessageProcessor.Value.BeginProcessingMessageAsync(actions, args.Message, linkedCts.Token).ConfigureAwait(false)) { return; } ServiceBusTriggerInput input = ServiceBusTriggerInput.CreateSingle(args.Message, actions, _client.Value); TriggeredFunctionData data = input.GetTriggerFunctionData(); FunctionResult result = await _triggerExecutor.TryExecuteAsync(data, linkedCts.Token).ConfigureAwait(false); if (actions.ShouldReleaseSession) { args.ReleaseSession(); } await _sessionMessageProcessor.Value.CompleteProcessingMessageAsync(actions, args.Message, result, linkedCts.Token).ConfigureAwait(false); } }
public void BindSingle_Returns_Exptected_Message() { string data = "123"; var strategy = new ServiceBusTriggerBindingStrategy(); ServiceBusTriggerInput triggerInput = strategy.ConvertFromString(data); var contract = strategy.GetBindingData(triggerInput); ServiceBusReceivedMessage single = strategy.BindSingle(triggerInput, null); string body = single.Body.ToString(); Assert.AreEqual(data, body); Assert.Null(contract["MessageReceiver"]); Assert.Null(contract["MessageSession"]); }
internal async Task ProcessMessageAsync(Message message, CancellationToken cancellationToken) { using (CancellationTokenSource linkedCts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, _cancellationTokenSource.Token)) { if (!await _messageProcessor.BeginProcessingMessageAsync(message, linkedCts.Token)) { return; } ServiceBusTriggerInput input = ServiceBusTriggerInput.CreateSingle(message); input.MessageReceiver = Receiver; TriggeredFunctionData data = input.GetTriggerFunctionData(); FunctionResult result = await _triggerExecutor.TryExecuteAsync(data, linkedCts.Token); await _messageProcessor.CompleteProcessingMessageAsync(message, result, linkedCts.Token); } }
internal async Task ProcessSessionMessageAsync(ProcessSessionMessageEventArgs args) { using (CancellationTokenSource linkedCts = CancellationTokenSource.CreateLinkedTokenSource(args.CancellationToken, _cancellationTokenSource.Token)) { var actions = new ServiceBusSessionMessageActions(args); if (!await _sessionMessageProcessor.BeginProcessingMessageAsync(actions, args.Message, linkedCts.Token).ConfigureAwait(false)) { return; } ServiceBusTriggerInput input = ServiceBusTriggerInput.CreateSingle(args.Message); input.MessageActions = actions; TriggeredFunctionData data = input.GetTriggerFunctionData(); FunctionResult result = await _triggerExecutor.TryExecuteAsync(data, linkedCts.Token).ConfigureAwait(false); await _sessionMessageProcessor.CompleteProcessingMessageAsync(actions, args.Message, result, linkedCts.Token).ConfigureAwait(false); } }
internal async Task ProcessSessionMessageAsync(ProcessSessionMessageEventArgs args) { using (CancellationTokenSource linkedCts = CancellationTokenSource.CreateLinkedTokenSource(args.CancellationToken, _cancellationTokenSource.Token)) { ServiceBusSessionReceiver receiver = (ServiceBusSessionReceiver)typeof(ProcessSessionMessageEventArgs).GetField("_sessionReceiver", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(args); if (!await _sessionMessageProcessor.BeginProcessingMessageAsync(receiver, args.Message, linkedCts.Token).ConfigureAwait(false)) { return; } ServiceBusTriggerInput input = ServiceBusTriggerInput.CreateSingle(args.Message); input.SessionReceiver = receiver; TriggeredFunctionData data = input.GetTriggerFunctionData(); FunctionResult result = await _triggerExecutor.TryExecuteAsync(data, linkedCts.Token).ConfigureAwait(false); await _sessionMessageProcessor.CompleteProcessingMessageAsync(receiver, args.Message, result, linkedCts.Token).ConfigureAwait(false); } }
public void GetBindingData_MultipleDispatch_ReturnsExpectedValue() { var messages = new Message[3] { new Message(Encoding.UTF8.GetBytes("Event 1")), new Message(Encoding.UTF8.GetBytes("Event 2")), new Message(Encoding.UTF8.GetBytes("Event 3")), }; foreach (var message in messages) { SystemPropertiesCollection sysProps = GetSystemProperties(); TestHelpers.SetField(message, "SystemProperties", sysProps); } var input = ServiceBusTriggerInput.CreateBatch(messages); var strategy = new ServiceBusTriggerBindingStrategy(); var bindingData = strategy.GetBindingData(input); Assert.Equal(15, bindingData.Count); Assert.Same(input.MessageReceiver as MessageReceiver, bindingData["MessageReceiver"]); Assert.Same(input.MessageReceiver as IMessageSession, bindingData["MessageSession"]); // verify an array was created for each binding data type Assert.Equal(messages.Length, ((int[])bindingData["DeliveryCountArray"]).Length); Assert.Equal(messages.Length, ((string[])bindingData["DeadLetterSourceArray"]).Length); Assert.Equal(messages.Length, ((string[])bindingData["LockTokenArray"]).Length); Assert.Equal(messages.Length, ((DateTime[])bindingData["ExpiresAtUtcArray"]).Length); Assert.Equal(messages.Length, ((DateTime[])bindingData["EnqueuedTimeUtcArray"]).Length); Assert.Equal(messages.Length, ((string[])bindingData["MessageIdArray"]).Length); Assert.Equal(messages.Length, ((string[])bindingData["ContentTypeArray"]).Length); Assert.Equal(messages.Length, ((string[])bindingData["ReplyToArray"]).Length); Assert.Equal(messages.Length, ((long[])bindingData["SequenceNumberArray"]).Length); Assert.Equal(messages.Length, ((string[])bindingData["ToArray"]).Length); Assert.Equal(messages.Length, ((string[])bindingData["LabelArray"]).Length); Assert.Equal(messages.Length, ((string[])bindingData["CorrelationIdArray"]).Length); Assert.Equal(messages.Length, ((IDictionary <string, object>[])bindingData["UserPropertiesArray"]).Length); }
public void GetBindingData_SingleDispatch_ReturnsExpectedValue() { var message = new Message(new byte[] { }); SystemPropertiesCollection sysProp = GetSystemProperties(); TestHelpers.SetField(message, "SystemProperties", sysProp); IDictionary <string, object> userProps = new Dictionary <string, object>(); userProps.Add(new KeyValuePair <string, object>("prop1", "value1")); userProps.Add(new KeyValuePair <string, object>("prop2", "value2")); TestHelpers.SetField(message, "UserProperties", userProps); var input = ServiceBusTriggerInput.CreateSingle(message); var strategy = new ServiceBusTriggerBindingStrategy(); var bindingData = strategy.GetBindingData(input); Assert.Equal(15, bindingData.Count); // SystemPropertiesCollection is sealed Assert.Same(input.MessageReceiver as MessageReceiver, bindingData["MessageReceiver"]); Assert.Same(input.MessageReceiver as IMessageSession, bindingData["MessageSession"]); Assert.Equal(message.SystemProperties.LockToken, bindingData["LockToken"]); Assert.Equal(message.SystemProperties.SequenceNumber, bindingData["SequenceNumber"]); Assert.Equal(message.SystemProperties.DeliveryCount, bindingData["DeliveryCount"]); Assert.Same(message.SystemProperties.DeadLetterSource, bindingData["DeadLetterSource"]); Assert.Equal(message.ExpiresAtUtc, bindingData["ExpiresAtUtc"]); Assert.Same(message.MessageId, bindingData["MessageId"]); Assert.Same(message.ContentType, bindingData["ContentType"]); Assert.Same(message.ReplyTo, bindingData["ReplyTo"]); Assert.Same(message.To, bindingData["To"]); Assert.Same(message.Label, bindingData["Label"]); Assert.Same(message.CorrelationId, bindingData["CorrelationId"]); IDictionary <string, object> bindingDataUserProps = bindingData["UserProperties"] as Dictionary <string, object>; Assert.NotNull(bindingDataUserProps); Assert.Equal("value1", bindingDataUserProps["prop1"]); Assert.Equal("value2", bindingDataUserProps["prop2"]); }
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; } } } } }
internal void StartMessageBatchReceiver(CancellationToken cancellationToken) { ServiceBusClient sessionClient = null; ServiceBusReceiver receiver = null; if (_isSessionsEnabled) { sessionClient = _sessionClient.Value; } else { receiver = BatchReceiver; } Task.Run(async() => { while (true) { try { if (!_started || cancellationToken.IsCancellationRequested) { _logger.LogInformation("Message processing has been stopped or cancelled"); return; } if (_isSessionsEnabled && (receiver == null || receiver.IsClosed)) { try { receiver = await sessionClient.AcceptNextSessionAsync(_entityPath, new ServiceBusSessionReceiverOptions { PrefetchCount = _serviceBusOptions.PrefetchCount }).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.MaxMessages).ConfigureAwait(false); if (messages != null) { ServiceBusReceivedMessage[] messagesArray = messages.ToArray(); ServiceBusTriggerInput input = ServiceBusTriggerInput.CreateBatch(messagesArray); if (_isSessionsEnabled) { input.MessageActions = new ServiceBusSessionMessageActions((ServiceBusSessionReceiver)receiver); } else { input.MessageActions = new ServiceBusMessageActions(receiver); } FunctionResult result = await _triggerExecutor.TryExecuteAsync(input.GetTriggerFunctionData(), cancellationToken).ConfigureAwait(false); if (cancellationToken.IsCancellationRequested) { return; } // Complete batch of messages only if the execution was successful if (_serviceBusOptions.AutoCompleteMessages && _started) { if (result.Succeeded) { List <Task> completeTasks = new List <Task>(); foreach (ServiceBusReceivedMessage message in messagesArray) { completeTasks.Add(receiver.CompleteMessageAsync(message)); } await Task.WhenAll(completeTasks).ConfigureAwait(false); } else { List <Task> abandonTasks = new List <Task>(); foreach (ServiceBusReceivedMessage message in messagesArray) { abandonTasks.Add(receiver.AbandonMessageAsync(message)); } await Task.WhenAll(abandonTasks).ConfigureAwait(false); } } } else { // Close the session and release the session lock after draining all messages for the accepted session. if (_isSessionsEnabled) { await receiver.CloseAsync().ConfigureAwait(false); } } } catch (ObjectDisposedException) { // Ignore as we are stopping the host } catch (Exception ex) { // Log another exception _logger.LogError(ex, $"An unhandled exception occurred in the message batch receive loop"); if (_isSessionsEnabled && receiver != null) { // Attempt to close the session and release session lock to accept a new session on the next loop iteration try { await receiver.CloseAsync().ConfigureAwait(false); } catch { // Best effort receiver = null; } } } } }, cancellationToken); }
internal void StartMessageBatchReceiver(CancellationToken cancellationToken) { SessionClient sessionClient = null; IMessageReceiver receiver = null; if (_isSessionsEnabled) { sessionClient = _sessionClient.Value; } else { receiver = Receiver; } Task.Run(async() => { while (true) { try { if (!_started || cancellationToken.IsCancellationRequested) { _logger.LogInformation("Message processing has been stopped or cancelled"); return; } if (_isSessionsEnabled && (receiver == null || receiver.IsClosedOrClosing)) { try { receiver = await sessionClient.AcceptMessageSessionAsync(); receiver.PrefetchCount = _serviceBusOptions.PrefetchCount; } catch (ServiceBusTimeoutException) { // it's expected if the entity is empty, try next time continue; } } IList <Message> messages = await receiver.ReceiveAsync(_serviceBusOptions.BatchOptions.MaxMessageCount, _serviceBusOptions.BatchOptions.OperationTimeout); if (messages != null) { Message[] messagesArray = messages.ToArray(); ServiceBusTriggerInput input = ServiceBusTriggerInput.CreateBatch(messagesArray); input.MessageReceiver = receiver; FunctionResult result = await _triggerExecutor.TryExecuteAsync(input.GetTriggerFunctionData(), cancellationToken); if (cancellationToken.IsCancellationRequested) { return; } // Complete batch of messages only if the execution was successful if (_serviceBusOptions.BatchOptions.AutoComplete && _started) { if (result.Succeeded) { await receiver.CompleteAsync(messagesArray.Select(x => x.SystemProperties.LockToken)); } else { List <Task> abandonTasks = new List <Task>(); foreach (var lockTocken in messagesArray.Select(x => x.SystemProperties.LockToken)) { abandonTasks.Add(receiver.AbandonAsync(lockTocken)); } await Task.WhenAll(abandonTasks); } } } else { // Close the session and release the session lock after draining all messages for the accepted session. if (_isSessionsEnabled) { await receiver.CloseAsync(); } } } catch (ObjectDisposedException) { // Ignore as we are stopping the host } catch (Exception ex) { // Log another exception _logger.LogError($"An unhandled exception occurred in the message batch receive loop", ex); if (_isSessionsEnabled && receiver != null) { // Attempt to close the session and release session lock to accept a new session on the next loop iteration try { await receiver.CloseAsync(); } catch { // Best effort receiver = null; } } } } }, cancellationToken); }
internal void StartMessageBatchReceiver(CancellationToken cancellationToken) { SessionClient sessionClient = null; IMessageReceiver receiver = null; if (_isSessionsEnabled) { sessionClient = _messagingProvider.CreateSessionClient(_entityPath, _serviceBusAccount.ConnectionString); } else { receiver = Receiver; } Task.Run(async() => { while (true) { try { if (!_started || cancellationToken.IsCancellationRequested) { return; } if (_isSessionsEnabled) { try { receiver = await sessionClient.AcceptMessageSessionAsync(); } catch (ServiceBusTimeoutException) { // it's expected if the entity is empty, try next time continue; } } IList <Message> messages = await receiver.ReceiveAsync(_serviceBusOptions.BatchOptions.MaxMessageCount, _serviceBusOptions.BatchOptions.OperationTimeout); if (messages != null) { Message[] messagesArray = messages.ToArray(); ServiceBusTriggerInput input = ServiceBusTriggerInput.CreateBatch(messagesArray); input.MessageReceiver = receiver; FunctionResult result = await _triggerExecutor.TryExecuteAsync(input.GetTriggerFunctionData(), cancellationToken); if (cancellationToken.IsCancellationRequested) { return; } // Complete batch of messages only if the execution was successful if (_serviceBusOptions.BatchOptions.AutoComplete && _started) { if (result.Succeeded) { await receiver.CompleteAsync(messagesArray.Select(x => x.SystemProperties.LockToken)); } else { // Delivery count is not incremented if // Session is accepted, the messages within the session are not completed (even if they are locked), and the session is closed // https://docs.microsoft.com/en-us/azure/service-bus-messaging/message-sessions#impact-of-delivery-count if (_isSessionsEnabled) { List <Task> abandonTasks = new List <Task>(); foreach (var lockTocken in messagesArray.Select(x => x.SystemProperties.LockToken)) { abandonTasks.Add(receiver.AbandonAsync(lockTocken)); } await Task.WhenAll(abandonTasks); } } } // Close the session and release the session lock if (_isSessionsEnabled) { await receiver.CloseAsync(); } } } catch (ObjectDisposedException) { // Ignore as we are stopping the host } catch (Exception ex) { // Log another exception _logger.LogError($"An unhandled exception occurred in the message batch receive loop: {ex.ToString()}"); } } }, cancellationToken); }
private async Task RunBatchReceiveLoopAsync(CancellationToken cancellationToken) { ServiceBusClient sessionClient = null; ServiceBusReceiver receiver = null; if (_isSessionsEnabled) { sessionClient = _client.Value; } else { receiver = _batchReceiver.Value; } while (true) { try { if (cancellationToken.IsCancellationRequested) { _logger.LogInformation("Message processing has been stopped or cancelled"); 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) { ServiceBusReceivedMessage[] messagesArray = messages.ToArray(); ServiceBusTriggerInput input = ServiceBusTriggerInput.CreateBatch(messagesArray); if (_isSessionsEnabled) { input.MessageActions = new ServiceBusSessionMessageActions((ServiceBusSessionReceiver)receiver); } else { input.MessageActions = new ServiceBusMessageActions(receiver); } FunctionResult result = await _triggerExecutor.TryExecuteAsync(input.GetTriggerFunctionData(), cancellationToken).ConfigureAwait(false); // Complete batch of messages only if the execution was successful if (_autoCompleteMessagesOptionEvaluatedValue) { if (result.Succeeded) { List <Task> completeTasks = new List <Task>(); foreach (ServiceBusReceivedMessage message in messagesArray) { // skip messages that were settled in the user's function if (input.MessageActions.SettledMessages.Contains(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 { List <Task> abandonTasks = new List <Task>(); foreach (ServiceBusReceivedMessage message in messagesArray) { // skip messages that were settled in the user's function if (input.MessageActions.SettledMessages.Contains(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); } } } 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"); } catch (Exception ex) { // Log another exception _logger.LogError(ex, $"An unhandled exception occurred in the message batch receive loop"); 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; } } } } }