/// <summary> /// Receives a batch of <see cref="ServiceBusMessage" /> from the Service Bus entity partition. /// </summary> /// /// <param name="maximumMessageCount">The maximum number of messages to receive in this batch.</param> /// <param name="timeout"></param> /// <param name="cancellationToken">An optional <see cref="CancellationToken"/> instance to signal the request to cancel the operation.</param> /// /// <returns>The batch of <see cref="ServiceBusMessage" /> from the Service Bus entity partition this consumer is associated with. If no events are present, an empty enumerable is returned.</returns> /// internal async Task <IEnumerable <ServiceBusMessage> > ReceiveAsyncInternal( int maximumMessageCount, TimeSpan timeout, CancellationToken cancellationToken) { Argument.AssertNotClosed(_closed, nameof(AmqpConsumer)); Argument.AssertAtLeast(maximumMessageCount, 1, nameof(maximumMessageCount)); var link = default(ReceivingAmqpLink); var amqpMessages = default(IEnumerable <AmqpMessage>); var receivedMessages = default(List <ServiceBusMessage>); var stopWatch = Stopwatch.StartNew(); ServiceBusEventSource.Log.MessageReceiveStart(EntityName); link = await ReceiveLink.GetOrCreateAsync(UseMinimum(ConnectionScope.SessionTimeout, timeout)).ConfigureAwait(false); cancellationToken.ThrowIfCancellationRequested <TaskCanceledException>(); var messagesReceived = await Task.Factory.FromAsync ( (callback, state) => link.BeginReceiveRemoteMessages(maximumMessageCount, TimeSpan.FromMilliseconds(20), timeout, callback, state), (asyncResult) => link.EndReceiveMessages(asyncResult, out amqpMessages), TaskCreationOptions.RunContinuationsAsynchronously ).ConfigureAwait(false); cancellationToken.ThrowIfCancellationRequested <TaskCanceledException>(); // If event messages were received, then package them for consumption and // return them. if ((messagesReceived) && (amqpMessages != null)) { receivedMessages = new List <ServiceBusMessage>(); foreach (AmqpMessage message in amqpMessages) { if (_receiveMode == ReceiveMode.ReceiveAndDelete) { link.DisposeDelivery(message, true, AmqpConstants.AcceptedOutcome); } receivedMessages.Add(AmqpMessageConverter.AmqpMessageToSBMessage(message)); // message.Dispose(); } stopWatch.Stop(); return(receivedMessages); } stopWatch.Stop(); // No events were available. return(Enumerable.Empty <ServiceBusMessage>()); }
/// <summary> /// Receives a batch of <see cref="ServiceBusMessage" /> from the Service Bus entity partition. /// </summary> /// /// <param name="maximumMessageCount">The maximum number of messages to receive in this batch.</param> /// <param name="maximumWaitTime">The maximum amount of time to wait to build up the requested message count for the batch; if not specified, the per-try timeout specified by the retry policy will be used.</param> /// <param name="cancellationToken">An optional <see cref="CancellationToken"/> instance to signal the request to cancel the operation.</param> /// /// <returns>The batch of <see cref="ServiceBusMessage" /> from the Service Bus entity partition this consumer is associated with. If no events are present, an empty enumerable is returned.</returns> /// public override async Task <IEnumerable <ServiceBusMessage> > ReceiveAsync( int maximumMessageCount, TimeSpan?maximumWaitTime, CancellationToken cancellationToken) { Argument.AssertNotClosed(_closed, nameof(AmqpConsumer)); Argument.AssertAtLeast(maximumMessageCount, 1, nameof(maximumMessageCount)); var receivedMessageCount = 0; var failedAttemptCount = 0; var tryTimeout = _retryPolicy.CalculateTryTimeout(0); var waitTime = (maximumWaitTime ?? tryTimeout); var link = default(ReceivingAmqpLink); var retryDelay = default(TimeSpan?); var amqpMessages = default(IEnumerable <AmqpMessage>); var receivedMessages = default(List <ServiceBusMessage>); var stopWatch = Stopwatch.StartNew(); try { while (!cancellationToken.IsCancellationRequested) { try { ServiceBusEventSource.Log.MessageReceiveStart(EntityName); link = await ReceiveLink.GetOrCreateAsync(UseMinimum(ConnectionScope.SessionTimeout, tryTimeout)).ConfigureAwait(false); cancellationToken.ThrowIfCancellationRequested <TaskCanceledException>(); var messagesReceived = await Task.Factory.FromAsync ( (callback, state) => link.BeginReceiveRemoteMessages(maximumMessageCount, TimeSpan.FromMilliseconds(20), waitTime, callback, state), (asyncResult) => link.EndReceiveMessages(asyncResult, out amqpMessages), TaskCreationOptions.RunContinuationsAsynchronously ).ConfigureAwait(false); cancellationToken.ThrowIfCancellationRequested <TaskCanceledException>(); // If event messages were received, then package them for consumption and // return them. if ((messagesReceived) && (amqpMessages != null)) { receivedMessages ??= new List <ServiceBusMessage>(); foreach (AmqpMessage message in amqpMessages) { //link.DisposeDelivery(message, true, AmqpConstants.AcceptedOutcome); receivedMessages.Add(AmqpMessageConverter.AmqpMessageToSBMessage(message)); message.Dispose(); } receivedMessageCount = receivedMessages.Count; return(receivedMessages); } // No events were available. return(Enumerable.Empty <ServiceBusMessage>()); } catch (ServiceBusException ex) when(ex.Reason == ServiceBusException.FailureReason.ServiceTimeout) { // Because the timeout specified with the request is intended to be the maximum // amount of time to wait for events, a timeout isn't considered an error condition, // rather a sign that no events were available in the requested period. return(Enumerable.Empty <ServiceBusMessage>()); } catch (Exception ex) { Exception activeEx = ex.TranslateServiceException(EntityName); // Determine if there should be a retry for the next attempt; if so enforce the delay but do not quit the loop. // Otherwise, bubble the exception. ++failedAttemptCount; retryDelay = _retryPolicy.CalculateRetryDelay(activeEx, failedAttemptCount); if ((retryDelay.HasValue) && (!ConnectionScope.IsDisposed) && (!cancellationToken.IsCancellationRequested)) { ServiceBusEventSource.Log.MessageReceiveError(EntityName, activeEx.Message); await Task.Delay(UseMinimum(retryDelay.Value, waitTime.CalculateRemaining(stopWatch.Elapsed)), cancellationToken).ConfigureAwait(false); tryTimeout = _retryPolicy.CalculateTryTimeout(failedAttemptCount); } else if (ex is AmqpException) { throw activeEx; } else { throw; } } } // If no value has been returned nor exception thrown by this point, // then cancellation has been requested. throw new TaskCanceledException(); } catch (Exception ex) { ServiceBusEventSource.Log.MessageReceiveError(EntityName, ex.Message); throw; } finally { stopWatch.Stop(); ServiceBusEventSource.Log.MessageReceiveComplete(EntityName, receivedMessageCount); } }
/// <summary> /// /// </summary> /// <param name="retryPolicy"></param> /// <param name="fromSequenceNumber"></param> /// <param name="messageCount"></param> /// <param name="sessionId"></param> /// <param name="receiveLinkName"></param> /// <param name="timeout"></param> /// <param name="cancellationToken"></param> /// <returns></returns> internal async Task <IEnumerable <ServiceBusMessage> > PeekInternal( ServiceBusRetryPolicy retryPolicy, long?fromSequenceNumber, int messageCount, string sessionId, string receiveLinkName, TimeSpan timeout, CancellationToken cancellationToken = default) { var stopWatch = new Stopwatch(); stopWatch.Start(); AmqpRequestMessage amqpRequestMessage = AmqpRequestMessage.CreateRequest( ManagementConstants.Operations.PeekMessageOperation, timeout, null); await AquireAccessTokenAsync(cancellationToken).ConfigureAwait(false); if (receiveLinkName != null) { // include associated link for service optimization amqpRequestMessage.AmqpMessage.ApplicationProperties.Map[ManagementConstants.Request.AssociatedLinkName] = receiveLinkName; } amqpRequestMessage.Map[ManagementConstants.Properties.FromSequenceNumber] = fromSequenceNumber ?? LastPeekedSequenceNumber + 1; amqpRequestMessage.Map[ManagementConstants.Properties.MessageCount] = messageCount; if (!string.IsNullOrWhiteSpace(sessionId)) { amqpRequestMessage.Map[ManagementConstants.Properties.SessionId] = sessionId; } RequestResponseAmqpLink link = await ManagementLink.GetOrCreateAsync( UseMinimum(ConnectionScope.SessionTimeout, timeout.CalculateRemaining(stopWatch.Elapsed))) .ConfigureAwait(false); cancellationToken.ThrowIfCancellationRequested <TaskCanceledException>(); using AmqpMessage responseAmqpMessage = await link.RequestAsync( amqpRequestMessage.AmqpMessage, timeout.CalculateRemaining(stopWatch.Elapsed)) .ConfigureAwait(false); cancellationToken.ThrowIfCancellationRequested <TaskCanceledException>(); AmqpResponseMessage amqpResponseMessage = AmqpResponseMessage.CreateResponse(responseAmqpMessage); var messages = new List <ServiceBusMessage>(); //AmqpError.ThrowIfErrorResponse(responseAmqpMessage, EntityName); if (amqpResponseMessage.StatusCode == AmqpResponseStatusCode.OK) { ServiceBusMessage message = null; IEnumerable <AmqpMap> messageList = amqpResponseMessage.GetListValue <AmqpMap>(ManagementConstants.Properties.Messages); foreach (AmqpMap entry in messageList) { var payload = (ArraySegment <byte>)entry[ManagementConstants.Properties.Message]; var amqpMessage = AmqpMessage.CreateAmqpStreamMessage(new BufferListStream(new[] { payload }), true); message = AmqpMessageConverter.AmqpMessageToSBMessage(amqpMessage, true); messages.Add(message); } if (message != null) { LastPeekedSequenceNumber = message.SystemProperties.SequenceNumber; } return(messages); } if (amqpResponseMessage.StatusCode == AmqpResponseStatusCode.NoContent || (amqpResponseMessage.StatusCode == AmqpResponseStatusCode.NotFound && Equals(AmqpClientConstants.MessageNotFoundError, amqpResponseMessage.GetResponseErrorCondition()))) { return(messages); } // TODO throw correct exception throw new Exception(); }