private async Task SinglePhaseCommitAsync(SinglePhaseEnlistment singlePhaseEnlistment) { try { FaultTolerantAmqpObject <Controller> faultTolerantController = _connectionScope.TransactionController; Controller controller = await faultTolerantController.GetOrCreateAsync(_timeout) .ConfigureAwait(false); await controller.DischargeAsync(AmqpTransactionId, fail : false).ConfigureAwait(false); singlePhaseEnlistment.Committed(); ServiceBusEventSource.Log.TransactionDischarged( _transactionId, AmqpTransactionId, false); await CloseAsync().ConfigureAwait(false); } catch (Exception e) { Exception exception = AmqpExceptionHelper.TranslateException(e, null); ServiceBusEventSource.Log.TransactionDischargeException( _transactionId, AmqpTransactionId, exception); singlePhaseEnlistment.InDoubt(exception); } }
public static Exception ToMessagingContractException(this AmqpMessage responseMessage, AmqpResponseStatusCode statusCode) { AmqpSymbol errorCondition = AmqpExceptionHelper.GetResponseErrorCondition(responseMessage, statusCode); var statusDescription = responseMessage.ApplicationProperties.Map[ManagementConstants.Response.StatusDescription] as string ?? errorCondition.Value; return(AmqpExceptionHelper.ToMessagingContractException(errorCondition.Value, statusDescription)); }
/// <summary> /// /// </summary> /// <param name="sequenceNumbers"></param> /// <param name="timeout"></param> /// <param name="cancellationToken"></param> /// <returns></returns> internal async Task CancelScheduledMessageInternalAsync( long[] sequenceNumbers, TimeSpan timeout, CancellationToken cancellationToken = default) { var sendLink = default(SendingAmqpLink); try { var request = AmqpRequestMessage.CreateRequest( ManagementConstants.Operations.CancelScheduledMessageOperation, timeout, null); if (_sendLink.TryGetOpenedObject(out sendLink)) { request.AmqpMessage.ApplicationProperties.Map[ManagementConstants.Request.AssociatedLinkName] = sendLink.Name; } request.Map[ManagementConstants.Properties.SequenceNumbers] = sequenceNumbers; AmqpResponseMessage amqpResponseMessage = await ManagementUtilities.ExecuteRequestResponseAsync( _connectionScope, _managementLink, request, _transactionGroup, timeout).ConfigureAwait(false); cancellationToken.ThrowIfCancellationRequested <TaskCanceledException>(); if (amqpResponseMessage.StatusCode != AmqpResponseStatusCode.OK) { throw amqpResponseMessage.ToMessagingContractException(); } } catch (Exception exception) { ExceptionDispatchInfo.Capture(AmqpExceptionHelper.TranslateException( exception, sendLink?.GetTrackingId(), null, HasLinkCommunicationError(sendLink))) .Throw(); throw; // will never be reached } }
private async Task RollbackAsync(SinglePhaseEnlistment singlePhaseEnlistment) { try { Controller controller = await GetController(_timeout).ConfigureAwait(false); await controller.DischargeAsync(AmqpTransactionId, fail : true).ConfigureAwait(false); singlePhaseEnlistment.Aborted(); ServiceBusEventSource.Log.TransactionDischarged(_transactionId, AmqpTransactionId, true); } catch (Exception e) { Exception exception = AmqpExceptionHelper.TranslateException(e, null); ServiceBusEventSource.Log.TransactionDischargeException( _transactionId, AmqpTransactionId, exception); singlePhaseEnlistment.Aborted(exception); } }
private async Task RollbackAsync(SinglePhaseEnlistment singlePhaseEnlistment) { try { FaultTolerantAmqpObject <Controller> faultTolerantController = _connectionScope.TransactionController; Controller controller = await faultTolerantController.GetOrCreateAsync(_timeout) .ConfigureAwait(false); await controller.DischargeAsync(AmqpTransactionId, fail : true).ConfigureAwait(false); singlePhaseEnlistment.Aborted(); MessagingEventSource.Log.AmqpTransactionDischarged(_transactionId, AmqpTransactionId, true); } catch (Exception e) { Exception exception = AmqpExceptionHelper.TranslateException(e, null); MessagingEventSource.Log.AmqpTransactionDischargeException( _transactionId, AmqpTransactionId, exception); singlePhaseEnlistment.Aborted(exception); } }
/// <summary> /// Creates an AMQP link for use with publishing operations. /// </summary> /// <param name="entityPath"></param> /// <param name="viaEntityPath">The entity path to route the message through. Useful when using transactions.</param> /// <param name="connection">The active and opened AMQP connection to use for this link.</param> /// <param name="timeout">The timeout to apply when creating the link.</param> /// <param name="cancellationToken">An optional <see cref="CancellationToken"/> instance to signal the request to cancel the operation.</param> /// /// <returns>A link for use for operations related to receiving events.</returns> /// protected virtual async Task <SendingAmqpLink> CreateSendingLinkAsync( string entityPath, string viaEntityPath, AmqpConnection connection, TimeSpan timeout, CancellationToken cancellationToken) { Argument.AssertNotDisposed(IsDisposed, nameof(AmqpConnectionScope)); cancellationToken.ThrowIfCancellationRequested <TaskCanceledException>(); var session = default(AmqpSession); var stopWatch = Stopwatch.StartNew(); try { string[] audience; Uri destinationEndpoint = null; // if there is a via entityPath, include that in the audience if (!string.IsNullOrEmpty(viaEntityPath)) { destinationEndpoint = new Uri(ServiceEndpoint, viaEntityPath); var finalDestinationEndpoint = new Uri(ServiceEndpoint, entityPath); audience = new string[] { finalDestinationEndpoint.AbsoluteUri, destinationEndpoint.AbsoluteUri }; } else { destinationEndpoint = new Uri(ServiceEndpoint, entityPath); audience = new string[] { destinationEndpoint.AbsoluteUri }; } // Perform the initial authorization for the link. var authClaims = new[] { ServiceBusClaim.Send }; DateTime authExpirationUtc = await RequestAuthorizationUsingCbsAsync( connection, TokenProvider, destinationEndpoint, audience, authClaims, timeout.CalculateRemaining(stopWatch.Elapsed)) .ConfigureAwait(false); cancellationToken.ThrowIfCancellationRequested <TaskCanceledException>(); // Create and open the AMQP session associated with the link. var sessionSettings = new AmqpSessionSettings { Properties = new Fields() }; session = connection.CreateSession(sessionSettings); await OpenAmqpObjectAsync(session, timeout).ConfigureAwait(false); cancellationToken.ThrowIfCancellationRequested <TaskCanceledException>(); // Create and open the link. var linkSettings = new AmqpLinkSettings { Role = false, InitialDeliveryCount = 0, Source = new Source { Address = Guid.NewGuid().ToString() }, Target = new Target { Address = destinationEndpoint.AbsolutePath } }; if (!string.IsNullOrEmpty(viaEntityPath)) { linkSettings.AddProperty(AmqpClientConstants.TransferDestinationAddress, entityPath); } linkSettings.AddProperty(AmqpProperty.Timeout, (uint)timeout.CalculateRemaining(stopWatch.Elapsed).TotalMilliseconds); var link = new SendingAmqpLink(linkSettings); linkSettings.LinkName = $"{ Id };{ connection.Identifier }:{ session.Identifier }:{ link.Identifier }"; link.AttachTo(session); stopWatch.Stop(); // Configure refresh for authorization of the link. var refreshTimer = default(Timer); TimerCallback refreshHandler = CreateAuthorizationRefreshHandler ( entityPath, connection, link, TokenProvider, destinationEndpoint, audience, authClaims, AuthorizationRefreshTimeout, () => refreshTimer ); refreshTimer = new Timer(refreshHandler, null, CalculateLinkAuthorizationRefreshInterval(authExpirationUtc), Timeout.InfiniteTimeSpan); // Track the link before returning it, so that it can be managed with the scope. BeginTrackingLinkAsActive(entityPath, link, refreshTimer); return(link); } catch (Exception exception) { // Aborting the session will perform any necessary cleanup of // the associated link as well. session?.Abort(); throw AmqpExceptionHelper.TranslateException( exception, null, session.GetInnerException(), connection.IsClosing()); } }
/// <summary> /// Creates an AMQP link for use with receiving operations. /// </summary> /// <param name="entityPath"></param> /// /// <param name="connection">The active and opened AMQP connection to use for this link.</param> /// <param name="endpoint">The fully qualified endpoint to open the link for.</param> /// <param name="prefetchCount">Controls the number of events received and queued locally without regard to whether an operation was requested.</param> /// <param name="receiveMode">The <see cref="ReceiveMode"/> used to specify how messages are received. Defaults to PeekLock mode.</param> /// <param name="sessionId"></param> /// <param name="isSessionReceiver"></param> /// <param name="timeout">The timeout to apply when creating the link.</param> /// <param name="cancellationToken">An optional <see cref="CancellationToken"/> instance to signal the request to cancel the operation.</param> /// /// <returns>A link for use for operations related to receiving events.</returns> /// protected virtual async Task <ReceivingAmqpLink> CreateReceivingLinkAsync( string entityPath, AmqpConnection connection, Uri endpoint, TimeSpan timeout, uint prefetchCount, ReceiveMode receiveMode, string sessionId, bool isSessionReceiver, CancellationToken cancellationToken) { Argument.AssertNotDisposed(IsDisposed, nameof(AmqpConnectionScope)); cancellationToken.ThrowIfCancellationRequested <TaskCanceledException>(); var session = default(AmqpSession); var stopWatch = Stopwatch.StartNew(); try { // Perform the initial authorization for the link. string[] authClaims = new string[] { ServiceBusClaim.Send }; var audience = new[] { endpoint.AbsoluteUri }; DateTime authExpirationUtc = await RequestAuthorizationUsingCbsAsync( connection, TokenProvider, endpoint, audience, authClaims, timeout.CalculateRemaining(stopWatch.Elapsed)).ConfigureAwait(false); cancellationToken.ThrowIfCancellationRequested <TaskCanceledException>(); // Create and open the AMQP session associated with the link. var sessionSettings = new AmqpSessionSettings { Properties = new Fields() }; session = connection.CreateSession(sessionSettings); await OpenAmqpObjectAsync(session, timeout).ConfigureAwait(false); cancellationToken.ThrowIfCancellationRequested <TaskCanceledException>(); var filters = new FilterSet(); // even if supplied sessionId is null, we need to add the Session filter if it is a session receiver if (isSessionReceiver) { filters.Add(AmqpClientConstants.SessionFilterName, sessionId); } var linkSettings = new AmqpLinkSettings { Role = true, TotalLinkCredit = prefetchCount, AutoSendFlow = prefetchCount > 0, SettleType = (receiveMode == ReceiveMode.PeekLock) ? SettleMode.SettleOnDispose : SettleMode.SettleOnSend, Source = new Source { Address = endpoint.AbsolutePath, FilterSet = filters }, Target = new Target { Address = Guid.NewGuid().ToString() } }; var link = new ReceivingAmqpLink(linkSettings); linkSettings.LinkName = $"{connection.Settings.ContainerId};{connection.Identifier}:{session.Identifier}:{link.Identifier}:{linkSettings.Source.ToString()}"; link.AttachTo(session); stopWatch.Stop(); // Configure refresh for authorization of the link. var refreshTimer = default(Timer); TimerCallback refreshHandler = CreateAuthorizationRefreshHandler ( entityPath, connection, link, TokenProvider, endpoint, audience, authClaims, AuthorizationRefreshTimeout, () => (ActiveLinks.ContainsKey(link) ? refreshTimer : null) ); refreshTimer = new Timer(refreshHandler, null, CalculateLinkAuthorizationRefreshInterval(authExpirationUtc), Timeout.InfiniteTimeSpan); // Track the link before returning it, so that it can be managed with the scope. BeginTrackingLinkAsActive(entityPath, link, refreshTimer); return(link); } catch (Exception exception) { // Aborting the session will perform any necessary cleanup of // the associated link as well. session?.Abort(); throw AmqpExceptionHelper.TranslateException( exception, null, session.GetInnerException(), connection.IsClosing()); } }
/// <summary> /// Creates an AMQP link for use with management operations. /// </summary> /// <param name="entityPath"></param> /// /// <param name="connection">The active and opened AMQP connection to use for this link.</param> /// <param name="timeout">The timeout to apply when creating the link.</param> /// <param name="cancellationToken">An optional <see cref="CancellationToken"/> instance to signal the request to cancel the operation.</param> /// /// <returns>A link for use with management operations.</returns> /// protected virtual async Task <RequestResponseAmqpLink> CreateManagementLinkAsync( string entityPath, AmqpConnection connection, TimeSpan timeout, CancellationToken cancellationToken) { Argument.AssertNotDisposed(IsDisposed, nameof(AmqpConnectionScope)); cancellationToken.ThrowIfCancellationRequested <TaskCanceledException>(); var session = default(AmqpSession); var stopWatch = Stopwatch.StartNew(); try { // Create and open the AMQP session associated with the link. var sessionSettings = new AmqpSessionSettings { Properties = new Fields() }; session = connection.CreateSession(sessionSettings); await OpenAmqpObjectAsync(session, timeout).ConfigureAwait(false); cancellationToken.ThrowIfCancellationRequested <TaskCanceledException>(); // Create and open the link. var linkSettings = new AmqpLinkSettings(); linkSettings.AddProperty(AmqpProperty.Timeout, (uint)timeout.CalculateRemaining(stopWatch.Elapsed).TotalMilliseconds); linkSettings.AddProperty(AmqpClientConstants.EntityTypeName, AmqpClientConstants.EntityTypeManagement); entityPath += '/' + AmqpClientConstants.ManagementAddress; // Perform the initial authorization for the link. string[] claims = { ServiceBusClaim.Manage, ServiceBusClaim.Listen, ServiceBusClaim.Send }; var endpoint = new Uri(ServiceEndpoint, entityPath); var audience = new[] { endpoint.AbsoluteUri }; DateTime authExpirationUtc = await RequestAuthorizationUsingCbsAsync( connection, TokenProvider, ServiceEndpoint, audience, claims, timeout.CalculateRemaining(stopWatch.Elapsed)) .ConfigureAwait(false); var link = new RequestResponseAmqpLink( AmqpClientConstants.EntityTypeManagement, session, entityPath, linkSettings.Properties); linkSettings.LinkName = $"{connection.Settings.ContainerId};{connection.Identifier}:{session.Identifier}:{link.Identifier}"; stopWatch.Stop(); // Track the link before returning it, so that it can be managed with the scope. var refreshTimer = default(Timer); TimerCallback refreshHandler = CreateAuthorizationRefreshHandler ( entityPath, connection, link, TokenProvider, ServiceEndpoint, audience, claims, AuthorizationRefreshTimeout, () => (ActiveLinks.ContainsKey(link) ? refreshTimer : null) ); refreshTimer = new Timer(refreshHandler, null, CalculateLinkAuthorizationRefreshInterval(authExpirationUtc), Timeout.InfiniteTimeSpan); // Track the link before returning it, so that it can be managed with the scope. BeginTrackingLinkAsActive(entityPath, link, refreshTimer); return(link); } catch (Exception exception) { // Aborting the session will perform any necessary cleanup of // the associated link as well. session?.Abort(); throw AmqpExceptionHelper.TranslateException( exception, null, session.GetInnerException(), connection.IsClosing()); } }
/// <summary> /// /// </summary> /// <param name="message"></param> /// <param name="timeout"></param> /// <param name="cancellationToken"></param> /// <returns></returns> internal async Task <long> ScheduleMessageInternalAsync( ServiceBusMessage message, TimeSpan timeout, CancellationToken cancellationToken = default) { var sendLink = default(SendingAmqpLink); try { using (AmqpMessage amqpMessage = AmqpMessageConverter.SBMessageToAmqpMessage(message)) { var request = AmqpRequestMessage.CreateRequest( ManagementConstants.Operations.ScheduleMessageOperation, timeout, null); if (_sendLink.TryGetOpenedObject(out sendLink)) { request.AmqpMessage.ApplicationProperties.Map[ManagementConstants.Request.AssociatedLinkName] = sendLink.Name; } ArraySegment <byte>[] payload = amqpMessage.GetPayload(); var buffer = new BufferListStream(payload); ArraySegment <byte> value = buffer.ReadBytes((int)buffer.Length); var entry = new AmqpMap(); { entry[ManagementConstants.Properties.Message] = value; entry[ManagementConstants.Properties.MessageId] = message.MessageId; if (!string.IsNullOrWhiteSpace(message.SessionId)) { entry[ManagementConstants.Properties.SessionId] = message.SessionId; } if (!string.IsNullOrWhiteSpace(message.PartitionKey)) { entry[ManagementConstants.Properties.PartitionKey] = message.PartitionKey; } if (!string.IsNullOrWhiteSpace(message.ViaPartitionKey)) { entry[ManagementConstants.Properties.ViaPartitionKey] = message.ViaPartitionKey; } } request.Map[ManagementConstants.Properties.Messages] = new List <AmqpMap> { entry }; AmqpResponseMessage amqpResponseMessage = await ManagementUtilities.ExecuteRequestResponseAsync( _connectionScope, _managementLink, request, timeout).ConfigureAwait(false); cancellationToken.ThrowIfCancellationRequested <TaskCanceledException>(); if (amqpResponseMessage.StatusCode == AmqpResponseStatusCode.OK) { var sequenceNumbers = amqpResponseMessage.GetValue <long[]>(ManagementConstants.Properties.SequenceNumbers); if (sequenceNumbers == null || sequenceNumbers.Length < 1) { throw new ServiceBusException(true, "Could not schedule message successfully."); } return(sequenceNumbers[0]); } else { throw amqpResponseMessage.ToMessagingContractException(); } } } catch (Exception exception) { ExceptionDispatchInfo.Capture(AmqpExceptionHelper.TranslateException( exception, sendLink?.GetTrackingId(), null, HasLinkCommunicationError(sendLink))) .Throw(); throw; // will never be reached } }
/// <summary> /// Sends a set of messages to the associated Queue/Topic using a batched approach. /// </summary> /// /// <param name="messageFactory"></param> /// <param name="timeout"></param> /// <param name="cancellationToken">An optional <see cref="CancellationToken"/> instance to signal the request to cancel the operation.</param> /// internal virtual async Task SendBatchInternalAsync( Func <AmqpMessage> messageFactory, TimeSpan timeout, CancellationToken cancellationToken) { var stopWatch = ValueStopwatch.StartNew(); var link = default(SendingAmqpLink); try { using (AmqpMessage batchMessage = messageFactory()) { string messageHash = batchMessage.GetHashCode().ToString(CultureInfo.InvariantCulture); ArraySegment <byte> transactionId = AmqpConstants.NullBinary; Transaction ambientTransaction = Transaction.Current; if (ambientTransaction != null) { transactionId = await AmqpTransactionManager.Instance.EnlistAsync( ambientTransaction, _connectionScope, timeout).ConfigureAwait(false); } link = await _sendLink.GetOrCreateAsync(UseMinimum(_connectionScope.SessionTimeout, timeout)).ConfigureAwait(false); // Validate that the message is not too large to send. This is done after the link is created to ensure // that the maximum message size is known, as it is dictated by the service using the link. if (batchMessage.SerializedMessageSize > MaxMessageSize) { throw new ServiceBusException(string.Format(CultureInfo.InvariantCulture, Resources.MessageSizeExceeded, messageHash, batchMessage.SerializedMessageSize, MaxMessageSize, _entityPath), ServiceBusException.FailureReason.MessageSizeExceeded); } // Attempt to send the message batch. var deliveryTag = new ArraySegment <byte>(BitConverter.GetBytes(Interlocked.Increment(ref _deliveryCount))); Outcome outcome = await link.SendMessageAsync( batchMessage, deliveryTag, transactionId, timeout.CalculateRemaining(stopWatch.GetElapsedTime())).ConfigureAwait(false); cancellationToken.ThrowIfCancellationRequested <TaskCanceledException>(); if (outcome.DescriptorCode != Accepted.Code) { throw (outcome as Rejected)?.Error.ToMessagingContractException(); } cancellationToken.ThrowIfCancellationRequested <TaskCanceledException>(); } } catch (Exception exception) { ExceptionDispatchInfo.Capture(AmqpExceptionHelper.TranslateException( exception, link?.GetTrackingId(), null, HasLinkCommunicationError(link))) .Throw(); throw; // will never be reached } }