/// <summary> /// Opens an AMQP link for use with producer operations. /// </summary> /// <param name="entityPath"></param> /// <param name="viaEntityPath">The entity path to route the message through. Useful when using transactions.</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 producer operations.</returns> /// public virtual async Task <SendingAmqpLink> OpenSenderLinkAsync( string entityPath, string viaEntityPath, TimeSpan timeout, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested <TaskCanceledException>(); var stopWatch = Stopwatch.StartNew(); AmqpConnection connection = await ActiveConnection.GetOrCreateAsync(timeout).ConfigureAwait(false); cancellationToken.ThrowIfCancellationRequested <TaskCanceledException>(); SendingAmqpLink link = await CreateSendingLinkAsync( entityPath, viaEntityPath, connection, timeout.CalculateRemaining(stopWatch.Elapsed), cancellationToken).ConfigureAwait(false); cancellationToken.ThrowIfCancellationRequested <TaskCanceledException>(); await OpenAmqpObjectAsync(link, timeout.CalculateRemaining(stopWatch.Elapsed)).ConfigureAwait(false); cancellationToken.ThrowIfCancellationRequested <TaskCanceledException>(); stopWatch.Stop(); return(link); }
/// <summary> /// Opens an AMQP link for use with consumer operations. /// </summary> /// /// <param name="prefetchCount">Controls the number of events received and queued locally without regard to whether an operation was requested.</param> /// <param name="ownerLevel">The relative priority to associate with the link; for a non-exclusive link, this value should be <c>null</c>.</param> /// <param name="sessionId"></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 consumer operations.</returns> /// public virtual async Task <ReceivingAmqpLink> OpenConsumerLinkAsync(TimeSpan timeout, uint prefetchCount, long?ownerLevel, string sessionId, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested <TaskCanceledException>(); var stopWatch = Stopwatch.StartNew(); var consumerEndpoint = new Uri(ServiceEndpoint, EntityName); var connection = await ActiveConnection.GetOrCreateAsync(timeout).ConfigureAwait(false); cancellationToken.ThrowIfCancellationRequested <TaskCanceledException>(); var link = await CreateReceivingLinkAsync( connection, consumerEndpoint, timeout.CalculateRemaining(stopWatch.Elapsed), prefetchCount, ownerLevel, sessionId, cancellationToken ).ConfigureAwait(false); cancellationToken.ThrowIfCancellationRequested <TaskCanceledException>(); await OpenAmqpObjectAsync(link, timeout.CalculateRemaining(stopWatch.Elapsed)).ConfigureAwait(false); cancellationToken.ThrowIfCancellationRequested <TaskCanceledException>(); stopWatch.Stop(); return(link); }
/// <summary> /// Opens an AMQP link for use with management operations. /// </summary> /// <param name="entityPath"></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> /// /// <remarks> /// The authorization for this link does not require periodic /// refreshing. /// </remarks> /// public virtual async Task <RequestResponseAmqpLink> OpenManagementLinkAsync( string entityPath, TimeSpan timeout, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested <TaskCanceledException>(); var stopWatch = ValueStopwatch.StartNew(); var connection = await ActiveConnection.GetOrCreateAsync(timeout).ConfigureAwait(false); cancellationToken.ThrowIfCancellationRequested <TaskCanceledException>(); var link = await CreateManagementLinkAsync( entityPath, connection, timeout.CalculateRemaining(stopWatch.GetElapsedTime()), cancellationToken).ConfigureAwait(false); cancellationToken.ThrowIfCancellationRequested <TaskCanceledException>(); await OpenAmqpObjectAsync(link, timeout.CalculateRemaining(stopWatch.GetElapsedTime())).ConfigureAwait(false); cancellationToken.ThrowIfCancellationRequested <TaskCanceledException>(); return(link); }
/// <summary> /// Opens an AMQP link for use with management operations. /// </summary> /// <param name="entityPath">The path for the entity.</param> /// <param name="identifier">The identifier for the sender or receiver that is opening a management 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> /// /// <remarks> /// The authorization for this link does not require periodic /// refreshing. /// </remarks> /// public virtual async Task <RequestResponseAmqpLink> OpenManagementLinkAsync( string entityPath, string identifier, TimeSpan timeout, CancellationToken cancellationToken) { ServiceBusEventSource.Log.CreateManagementLinkStart(identifier); try { cancellationToken.ThrowIfCancellationRequested <TaskCanceledException>(); var stopWatch = ValueStopwatch.StartNew(); var connection = await ActiveConnection.GetOrCreateAsync(timeout).ConfigureAwait(false); cancellationToken.ThrowIfCancellationRequested <TaskCanceledException>(); var link = await CreateManagementLinkAsync( entityPath, connection, timeout.CalculateRemaining(stopWatch.GetElapsedTime()), cancellationToken).ConfigureAwait(false); cancellationToken.ThrowIfCancellationRequested <TaskCanceledException>(); await OpenAmqpObjectAsync(link, timeout.CalculateRemaining(stopWatch.GetElapsedTime())).ConfigureAwait(false); cancellationToken.ThrowIfCancellationRequested <TaskCanceledException>(); ServiceBusEventSource.Log.CreateManagementLinkComplete(identifier); return(link); } catch (Exception ex) { ServiceBusEventSource.Log.CreateManagementLinkException(identifier, ex.ToString()); throw; } }
/// <summary> /// Opens an AMQP link for use with producer operations. /// </summary> /// <param name="entityName"></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 producer operations.</returns> /// public virtual async Task <SendingAmqpLink> OpenSenderLinkAsync( string entityName, TimeSpan timeout, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested <TaskCanceledException>(); var stopWatch = Stopwatch.StartNew(); var producerEndpoint = new Uri(ServiceEndpoint, entityName); var connection = await ActiveConnection.GetOrCreateAsync(timeout).ConfigureAwait(false); cancellationToken.ThrowIfCancellationRequested <TaskCanceledException>(); var link = await CreateSendingLinkAsync( entityName, connection, producerEndpoint, timeout.CalculateRemaining(stopWatch.Elapsed), cancellationToken).ConfigureAwait(false); cancellationToken.ThrowIfCancellationRequested <TaskCanceledException>(); await OpenAmqpObjectAsync(link, timeout.CalculateRemaining(stopWatch.Elapsed)).ConfigureAwait(false); cancellationToken.ThrowIfCancellationRequested <TaskCanceledException>(); stopWatch.Stop(); return(link); }
private async Task <Controller> CreateControllerAsync(TimeSpan timeout) { var stopWatch = ValueStopwatch.StartNew(); AmqpConnection connection = await ActiveConnection.GetOrCreateAsync(timeout.CalculateRemaining(stopWatch.GetElapsedTime())).ConfigureAwait(false); var sessionSettings = new AmqpSessionSettings { Properties = new Fields() }; AmqpSession amqpSession = null; Controller controller; try { amqpSession = connection.CreateSession(sessionSettings); await amqpSession.OpenAsync(timeout.CalculateRemaining(stopWatch.GetElapsedTime())).ConfigureAwait(false); controller = new Controller(amqpSession, timeout.CalculateRemaining(stopWatch.GetElapsedTime())); await controller.OpenAsync(timeout.CalculateRemaining(stopWatch.GetElapsedTime())).ConfigureAwait(false); } catch (Exception exception) { if (amqpSession != null) { await amqpSession.CloseAsync(timeout).ConfigureAwait(false); } ServiceBusEventSource.Log.CreateControllerException(ActiveConnection.ToString(), exception.ToString()); throw; } return(controller); }
/// <summary> /// Opens an AMQP link for use with producer operations. /// </summary> /// /// <param name="partitionId">The identifier of the Event Hub partition to which the link should be bound; if unbound, <c>null</c>.</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 producer operations.</returns> /// public virtual async Task <SendingAmqpLink> OpenProducerLinkAsync(string partitionId, TimeSpan timeout, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested <TaskCanceledException>(); var stopWatch = Stopwatch.StartNew(); var path = (string.IsNullOrEmpty(partitionId)) ? EventHubName : string.Format(PartitionProducerPathSuffixMask, EventHubName, partitionId); var producerEndpoint = new Uri(ServiceEndpoint, path); var connection = await ActiveConnection.GetOrCreateAsync(timeout).ConfigureAwait(false); cancellationToken.ThrowIfCancellationRequested <TaskCanceledException>(); var link = await CreateSendingLinkAsync(connection, producerEndpoint, timeout.CalculateRemaining(stopWatch.Elapsed), cancellationToken).ConfigureAwait(false); cancellationToken.ThrowIfCancellationRequested <TaskCanceledException>(); await OpenAmqpObjectAsync(link, timeout.CalculateRemaining(stopWatch.Elapsed)).ConfigureAwait(false); cancellationToken.ThrowIfCancellationRequested <TaskCanceledException>(); stopWatch.Stop(); return(link); }
/// <summary> /// Opens an AMQP link for use with consumer operations. /// </summary> /// /// <param name="consumerGroup">The name of the consumer group in the context of which events should be received.</param> /// <param name="partitionId">The identifier of the Event Hub partition from which events should be received.</param> /// <param name="eventPosition">The position of the event in the partition where the link should be filtered to.</param> /// <param name="consumerOptions">The set of active options for the consumer that will make use of the 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 consumer operations.</returns> /// public virtual async Task <ReceivingAmqpLink> OpenConsumerLinkAsync(string consumerGroup, string partitionId, EventPosition eventPosition, EventHubConsumerOptions consumerOptions, TimeSpan timeout, CancellationToken cancellationToken) { Argument.AssertNotNullOrEmpty(consumerGroup, nameof(consumerGroup)); Argument.AssertNotNullOrEmpty(partitionId, nameof(partitionId)); Argument.AssertNotNull(eventPosition, nameof(eventPosition)); Argument.AssertNotNull(consumerOptions, nameof(consumerOptions)); cancellationToken.ThrowIfCancellationRequested <TaskCanceledException>(); var stopWatch = Stopwatch.StartNew(); var consumerEndpoint = new Uri(ServiceEndpoint, string.Format(ConsumerPathSuffixMask, consumerGroup, partitionId)); var connection = await ActiveConnection.GetOrCreateAsync(timeout).ConfigureAwait(false); cancellationToken.ThrowIfCancellationRequested <TaskCanceledException>(); var link = await CreateReceivingLinkAsync(connection, consumerEndpoint, eventPosition, consumerOptions, timeout.CalculateRemaining(stopWatch.Elapsed), cancellationToken).ConfigureAwait(false); cancellationToken.ThrowIfCancellationRequested <TaskCanceledException>(); await OpenAmqpObjectAsync(link, timeout.CalculateRemaining(stopWatch.Elapsed)).ConfigureAwait(false); cancellationToken.ThrowIfCancellationRequested <TaskCanceledException>(); stopWatch.Stop(); return(link); }
/// <summary> /// Retrieves information about an Event Hub, including the number of partitions present /// and their identifiers. /// </summary> /// /// <param name="cancellationToken">An optional <see cref="CancellationToken"/> instance to signal the request to cancel the operation.</param> /// /// <returns>The set of information for the Event Hub that this client is associated with.</returns> /// public override async Task <EventHubProperties> GetPropertiesAsync(CancellationToken cancellationToken) { // Since the AMQP objects do not honor the cancellation token, manually check for cancellation between operation steps. cancellationToken.ThrowIfCancellationRequested <TaskCanceledException>(); try { EventHubsEventSource.Log.GetPropertiesStart(EventHubName); // Create the request message and the management link. var token = await AquireAccessTokenAsync(cancellationToken).ConfigureAwait(false); using var request = MessageConverter.CreateEventHubPropertiesRequest(EventHubName, token); cancellationToken.ThrowIfCancellationRequested <TaskCanceledException>(); var stopWatch = Stopwatch.StartNew(); var link = await ManagementLink.GetOrCreateAsync(_tryTimeout).ConfigureAwait(false); // Send the request and wait for the response. stopWatch.Stop(); cancellationToken.ThrowIfCancellationRequested <TaskCanceledException>(); using var response = await link.RequestAsync(request, _tryTimeout.CalculateRemaining(stopWatch.Elapsed)).ConfigureAwait(false); // Process the response. cancellationToken.ThrowIfCancellationRequested <TaskCanceledException>(); ThrowIfErrorResponse(response, EventHubName); return(MessageConverter.CreateEventHubPropertiesFromResponse(response)); } catch (Exception ex) { EventHubsEventSource.Log.GetPropertiesError(EventHubName, ex.Message); throw; } finally { EventHubsEventSource.Log.GetPropertiesComplete(EventHubName); } }
/// <summary> /// /// </summary> /// <param name="sequenceNumber"></param> /// <param name="retryPolicy"></param> /// <param name="receiveLinkName"></param> /// <param name="timeout"></param> /// <param name="cancellationToken"></param> /// <returns></returns> internal async Task CancelScheduledMessageInternal( long sequenceNumber, ServiceBusRetryPolicy retryPolicy, string receiveLinkName, TimeSpan timeout, CancellationToken cancellationToken = default) { var stopWatch = Stopwatch.StartNew(); var request = AmqpRequestMessage.CreateRequest( ManagementConstants.Operations.CancelScheduledMessageOperation, timeout, null); if (receiveLinkName != null) { request.AmqpMessage.ApplicationProperties.Map[ManagementConstants.Request.AssociatedLinkName] = receiveLinkName; } request.Map[ManagementConstants.Properties.SequenceNumbers] = new[] { sequenceNumber }; RequestResponseAmqpLink link = await ManagementLink.GetOrCreateAsync( UseMinimum(ConnectionScope.SessionTimeout, timeout.CalculateRemaining(stopWatch.Elapsed))) .ConfigureAwait(false); using AmqpMessage response = await link.RequestAsync( request.AmqpMessage, timeout.CalculateRemaining(stopWatch.Elapsed)) .ConfigureAwait(false); cancellationToken.ThrowIfCancellationRequested <TaskCanceledException>(); stopWatch.Stop(); AmqpResponseMessage amqpResponseMessage = AmqpResponseMessage.CreateResponse(response); if (amqpResponseMessage.StatusCode != AmqpResponseStatusCode.OK) { throw new Exception(); //throw response.ToMessagingContractException(); } return; }
/// <summary> /// Opens an AMQP link for use with management operations. /// </summary> /// /// <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> OpenManagementLinkAsync(AmqpConnection connection, TimeSpan timeout, CancellationToken cancellationToken) { Guard.NotDisposed(nameof(AmqpConnectionScope), _disposed); 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 session.OpenAsync(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); var link = new RequestResponseAmqpLink(AmqpManagement.LinkType, session, AmqpManagement.Address, linkSettings.Properties); await link.OpenAsync(timeout.CalculateRemaining(stopWatch.Elapsed)).ConfigureAwait(false); stopWatch.Stop(); return(link); } catch { // Aborting the session will perform any necessary cleanup of // the associated link as well. session?.Abort(); throw; } }
/// <summary> /// Opens an AMQP link for use with management operations. /// </summary> /// /// <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> /// /// <remarks> /// The authorization for this link does not require periodic /// refreshing. /// </remarks> /// public async Task <RequestResponseAmqpLink> OpenManagementLinkAsync(TimeSpan timeout, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested <TaskCanceledException>(); var stopWatch = Stopwatch.StartNew(); var connection = await ActiveConnection.GetOrCreateAsync(timeout).ConfigureAwait(false); stopWatch.Stop(); return(await OpenManagementLinkAsync(connection, timeout.CalculateRemaining(stopWatch.Elapsed), cancellationToken).ConfigureAwait(false)); }
/// <summary> /// Opens an AMQP link for use with consumer operations. /// </summary> /// /// <param name="consumerGroup">The name of the consumer group in the context of which events should be received.</param> /// <param name="partitionId">The identifier of the Event Hub partition from which events should be received.</param> /// <param name="eventPosition">The position of the event in the partition where the link should be filtered to.</param> /// <param name="prefetchCount">Controls the number of events received and queued locally without regard to whether an operation was requested.</param> /// <param name="ownerLevel">The relative priority to associate with the link; for a non-exclusive link, this value should be <c>null</c>.</param> /// <param name="trackLastEnqueuedEventProperties">Indicates whether information on the last enqueued event on the partition is sent as events are received.</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 consumer operations.</returns> /// public virtual async Task <ReceivingAmqpLink> OpenConsumerLinkAsync(string consumerGroup, string partitionId, EventPosition eventPosition, TimeSpan timeout, uint prefetchCount, long?ownerLevel, bool trackLastEnqueuedEventProperties, CancellationToken cancellationToken) { Argument.AssertNotNullOrEmpty(consumerGroup, nameof(consumerGroup)); Argument.AssertNotNullOrEmpty(partitionId, nameof(partitionId)); cancellationToken.ThrowIfCancellationRequested <TaskCanceledException>(); var stopWatch = ValueStopwatch.StartNew(); var consumerEndpoint = new Uri(ServiceEndpoint, string.Format(CultureInfo.InvariantCulture, ConsumerPathSuffixMask, EventHubName, consumerGroup, partitionId)); var connection = await ActiveConnection.GetOrCreateAsync(timeout).ConfigureAwait(false); cancellationToken.ThrowIfCancellationRequested <TaskCanceledException>(); var link = await CreateReceivingLinkAsync( connection, consumerEndpoint, eventPosition, timeout.CalculateRemaining(stopWatch.GetElapsedTime()), prefetchCount, ownerLevel, trackLastEnqueuedEventProperties, cancellationToken ).ConfigureAwait(false); cancellationToken.ThrowIfCancellationRequested <TaskCanceledException>(); await OpenAmqpObjectAsync(link, timeout.CalculateRemaining(stopWatch.GetElapsedTime())).ConfigureAwait(false); cancellationToken.ThrowIfCancellationRequested <TaskCanceledException>(); return(link); }
/// <summary> /// Creates an AMQP connection for a given scope. /// </summary> /// /// <param name="amqpVersion">The version of AMQP to use for the connection.</param> /// <param name="serviceEndpoint">The endpoint for the Service Bus service to which the scope is associated.</param> /// <param name="transportType">The type of transport to use for communication.</param> /// <param name="proxy">The proxy, if any, to use for communication.</param> /// <param name="scopeIdentifier">The unique identifier for the associated scope.</param> /// <param name="timeout">The timeout to consider when creating the connection.</param> /// /// <returns>An AMQP connection that may be used for communicating with the Service Bus service.</returns> /// protected virtual async Task <AmqpConnection> CreateAndOpenConnectionAsync( Version amqpVersion, Uri serviceEndpoint, ServiceBusTransportType transportType, IWebProxy proxy, string scopeIdentifier, TimeSpan timeout) { var hostName = serviceEndpoint.Host; AmqpSettings amqpSettings = CreateAmpqSettings(AmqpVersion); AmqpConnectionSettings connectionSetings = CreateAmqpConnectionSettings(hostName, scopeIdentifier); TransportSettings transportSettings = transportType.IsWebSocketTransport() ? CreateTransportSettingsForWebSockets(hostName, proxy) : CreateTransportSettingsforTcp(hostName, serviceEndpoint.Port); // Create and open the connection, respecting the timeout constraint // that was received. var stopWatch = Stopwatch.StartNew(); var initiator = new AmqpTransportInitiator(amqpSettings, transportSettings); TransportBase transport = await initiator.ConnectTaskAsync(timeout).ConfigureAwait(false); var connection = new AmqpConnection(transport, amqpSettings, connectionSetings); await OpenAmqpObjectAsync(connection, timeout.CalculateRemaining(stopWatch.Elapsed)).ConfigureAwait(false); stopWatch.Stop(); // Create the CBS link that will be used for authorization. The act of creating the link will associate // it with the connection. new AmqpCbsLink(connection); // When the connection is closed, close each of the links associated with it. EventHandler closeHandler = null; closeHandler = (snd, args) => { foreach (var link in ActiveLinks.Keys) { link.SafeClose(); } connection.Closed -= closeHandler; }; connection.Closed += closeHandler; return(connection); }
/// <summary> /// Opens an AMQP link for use with consumer operations. /// </summary> /// <param name="entityPath"></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 with consumer operations.</returns> /// public virtual async Task <ReceivingAmqpLink> OpenReceiverLinkAsync( string entityPath, TimeSpan timeout, uint prefetchCount, ReceiveMode receiveMode, string sessionId, bool isSessionReceiver, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested <TaskCanceledException>(); var stopWatch = Stopwatch.StartNew(); var receiverEndpoint = new Uri(ServiceEndpoint, entityPath); var connection = await ActiveConnection.GetOrCreateAsync(timeout).ConfigureAwait(false); cancellationToken.ThrowIfCancellationRequested <TaskCanceledException>(); ReceivingAmqpLink link = await CreateReceivingLinkAsync( entityPath, connection, receiverEndpoint, timeout.CalculateRemaining(stopWatch.Elapsed), prefetchCount, receiveMode, sessionId, isSessionReceiver, cancellationToken ).ConfigureAwait(false); cancellationToken.ThrowIfCancellationRequested <TaskCanceledException>(); await OpenAmqpObjectAsync(link, timeout.CalculateRemaining(stopWatch.Elapsed)).ConfigureAwait(false); cancellationToken.ThrowIfCancellationRequested <TaskCanceledException>(); stopWatch.Stop(); return(link); }
/// <summary> /// /// </summary> /// <param name="sequenceNumber"></param> /// <param name="timeout"></param> /// <param name="cancellationToken"></param> /// <returns></returns> internal async Task CancelScheduledMessageInternalAsync( long sequenceNumber, TimeSpan timeout, CancellationToken cancellationToken = default) { var stopWatch = Stopwatch.StartNew(); var request = AmqpRequestMessage.CreateRequest( ManagementConstants.Operations.CancelScheduledMessageOperation, timeout, null); if (_sendLink.TryGetOpenedObject(out SendingAmqpLink sendLink)) { request.AmqpMessage.ApplicationProperties.Map[ManagementConstants.Request.AssociatedLinkName] = sendLink.Name; } request.Map[ManagementConstants.Properties.SequenceNumbers] = new[] { sequenceNumber }; RequestResponseAmqpLink mgmtLink = await _managementLink.GetOrCreateAsync( UseMinimum(_connectionScope.SessionTimeout, timeout.CalculateRemaining(stopWatch.Elapsed))) .ConfigureAwait(false); using AmqpMessage response = await mgmtLink.RequestAsync( request.AmqpMessage, timeout.CalculateRemaining(stopWatch.Elapsed)) .ConfigureAwait(false); cancellationToken.ThrowIfCancellationRequested <TaskCanceledException>(); stopWatch.Stop(); AmqpResponseMessage amqpResponseMessage = AmqpResponseMessage.CreateResponse(response); if (amqpResponseMessage.StatusCode != AmqpResponseStatusCode.OK) { throw amqpResponseMessage.ToMessagingContractException(); } }
/// <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 = Stopwatch.StartNew(); using (AmqpMessage batchMessage = messageFactory()) { string messageHash = batchMessage.GetHashCode().ToString(); ArraySegment <byte> transactionId = AmqpConstants.NullBinary; Transaction ambientTransaction = Transaction.Current; if (ambientTransaction != null) { transactionId = await AmqpTransactionManager.Instance.EnlistAsync( ambientTransaction, _connectionScope, timeout).ConfigureAwait(false); } SendingAmqpLink 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(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.Elapsed)).ConfigureAwait(false); cancellationToken.ThrowIfCancellationRequested <TaskCanceledException>(); if (outcome.DescriptorCode != Accepted.Code) { throw (outcome as Rejected)?.Error.ToMessagingContractException(); } cancellationToken.ThrowIfCancellationRequested <TaskCanceledException>(); stopWatch.Stop(); } }
/// <summary> /// Creates an AMQP connection for a given scope. /// </summary> /// /// <param name="amqpVersion">The version of AMQP to use for the connection.</param> /// <param name="serviceEndpoint">The endpoint for the Event Hubs service to which the scope is associated.</param> /// <param name="transportType">The type of transport to use for communication.</param> /// <param name="proxy">The proxy, if any, to use for communication.</param> /// <param name="scopeIdentifier">The unique identifier for the associated scope.</param> /// <param name="timeout">The timeout to consider when creating the connection.</param> /// /// <returns>An AMQP connection that may be used for communicating with the Event Hubs service.</returns> /// protected virtual async Task <AmqpConnection> CreateConnectionAsync(Version amqpVersion, Uri serviceEndpoint, TransportType transportType, IWebProxy proxy, string scopeIdentifier, TimeSpan timeout) { var hostName = serviceEndpoint.Host; var amqpSettings = CreateAmpqSettings(AmqpVersion); var connectionSetings = CreateAmqpConnectionSettings(hostName, scopeIdentifier); var transportSettings = transportType.IsWebSocketTransport() ? CreateTransportSettingsForWebSockets(hostName, proxy) : CreateTransportSettingsforTcp(hostName, serviceEndpoint.Port); // Create and open the connection, respecting the timeout constraint // that was received. var stopWatch = Stopwatch.StartNew(); var initiator = new AmqpTransportInitiator(amqpSettings, transportSettings); var transport = await initiator.ConnectTaskAsync(timeout).ConfigureAwait(false); var connection = new AmqpConnection(transport, amqpSettings, connectionSetings); await connection.OpenAsync(timeout.CalculateRemaining(stopWatch.Elapsed)).ConfigureAwait(false); stopWatch.Stop(); // Create the CBS link that will be used for authorization and ensure that it is associated // with the connection. var cbsLink = new AmqpCbsLink(connection); // TODO (pri2 // squire): // The act of creating the link should ensure that it is added to the connection. Unsure // of why this additional check was in the track one code. Investigate and either // document or remove. if (!connection.Extensions.Contains(typeof(AmqpCbsLink))) { connection.Extensions.Add(cbsLink); } return(connection); }
/// <summary> /// Creates an AMQP link for use with management operations. /// </summary> /// /// <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(AmqpConnection connection, TimeSpan timeout, CancellationToken cancellationToken) { Argument.AssertNotDisposed(IsDisposed, nameof(AmqpConnectionScope)); cancellationToken.ThrowIfCancellationRequested <TaskCanceledException>(); var session = default(AmqpSession); var stopWatch = ValueStopwatch.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.GetElapsedTime()).TotalMilliseconds); var link = new RequestResponseAmqpLink(AmqpManagement.LinkType, session, AmqpManagement.Address, linkSettings.Properties); // Track the link before returning it, so that it can be managed with the scope. BeginTrackingLinkAsActive(link); return(link); } catch { // Aborting the session will perform any necessary cleanup of // the associated link as well. session?.Abort(); throw; } }
/// <summary> /// /// </summary> /// <param name="message"></param> /// <param name="retryPolicy"></param> /// <param name="receiveLinkName"></param> /// <param name="cancellationToken"></param> /// <returns></returns> public override async Task <long> ScheduleMessageAsync( ServiceBusMessage message, ServiceBusRetryPolicy retryPolicy, string receiveLinkName = null, CancellationToken cancellationToken = default) { var failedAttemptCount = 0; var stopWatch = Stopwatch.StartNew(); try { TimeSpan tryTimeout = retryPolicy.CalculateTryTimeout(0); while (!cancellationToken.IsCancellationRequested) { try { using (AmqpMessage amqpMessage = AmqpMessageConverter.SBMessageToAmqpMessage(message)) { var request = AmqpRequestMessage.CreateRequest( ManagementConstants.Operations.ScheduleMessageOperation, tryTimeout, null); if (receiveLinkName != null) { request.AmqpMessage.ApplicationProperties.Map[ManagementConstants.Request.AssociatedLinkName] = receiveLinkName; } 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 }; RequestResponseAmqpLink link = await ManagementLink.GetOrCreateAsync( UseMinimum(ConnectionScope.SessionTimeout, tryTimeout.CalculateRemaining(stopWatch.Elapsed))) .ConfigureAwait(false); using AmqpMessage response = await link.RequestAsync( request.AmqpMessage, tryTimeout.CalculateRemaining(stopWatch.Elapsed)) .ConfigureAwait(false); cancellationToken.ThrowIfCancellationRequested <TaskCanceledException>(); stopWatch.Stop(); AmqpResponseMessage amqpResponseMessage = AmqpResponseMessage.CreateResponse(response); 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 new Exception(); //throw response.ToMessagingContractException(); } } } catch (Exception ex) { // Determine if there should be a retry for the next attempt; if so enforce the delay but do not quit the loop. // Otherwise, mark the exception as active and break out of the loop. ++failedAttemptCount; TimeSpan?retryDelay = retryPolicy.CalculateRetryDelay(ex, failedAttemptCount); if (retryDelay.HasValue && !ConnectionScope.IsDisposed && !cancellationToken.IsCancellationRequested) { ServiceBusEventSource.Log.ScheduleMessageError(EntityName, ex.Message); await Task.Delay(retryDelay.Value, cancellationToken).ConfigureAwait(false); tryTimeout = retryPolicy.CalculateTryTimeout(failedAttemptCount); stopWatch.Reset(); } } } // 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.ScheduleMessageError(EntityName, ex.Message); throw; } finally { stopWatch.Stop(); ServiceBusEventSource.Log.ScheduleMessageComplete(EntityName); } }
/// <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 receiving operations. /// </summary> /// /// <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="eventPosition">The position of the event in the partition where the link should be filtered to.</param> /// <param name="consumerOptions">The set of active options for the consumer that will make use of the 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 <ReceivingAmqpLink> CreateReceivingLinkAsync(AmqpConnection connection, Uri endpoint, EventPosition eventPosition, EventHubConsumerOptions consumerOptions, TimeSpan timeout, 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. var authClaims = new[] { EventHubsClaim.Listen }; var authExpirationUtc = await RequestAuthorizationUsingCbsAsync(connection, TokenProvider, endpoint, endpoint.AbsoluteUri, endpoint.AbsoluteUri, 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 filters = new FilterSet(); filters.Add(AmqpFilter.ConsumerFilterName, AmqpFilter.CreateConsumerFilter(AmqpFilter.BuildFilterExpression(eventPosition))); var linkSettings = new AmqpLinkSettings { Role = true, TotalLinkCredit = (uint)consumerOptions.PrefetchCount, AutoSendFlow = consumerOptions.PrefetchCount > 0, SettleType = SettleMode.SettleOnSend, Source = new Source { Address = endpoint.AbsolutePath, FilterSet = filters }, Target = new Target { Address = Guid.NewGuid().ToString() } }; linkSettings.AddProperty(AmqpProperty.EntityType, (int)AmqpProperty.Entity.ConsumerGroup); if (!string.IsNullOrEmpty(consumerOptions.Identifier)) { linkSettings.AddProperty(AmqpProperty.ConsumerIdentifier, consumerOptions.Identifier); } if (consumerOptions.OwnerLevel.HasValue) { linkSettings.AddProperty(AmqpProperty.OwnerLevel, consumerOptions.OwnerLevel.Value); } if (consumerOptions.TrackLastEnqueuedEventInformation) { linkSettings.DesiredCapabilities = new Multiple <AmqpSymbol>(new List <AmqpSymbol> { AmqpProperty.TrackLastEnqueuedEventInformation }); } var link = new ReceivingAmqpLink(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); var refreshHandler = CreateAuthorizationRefreshHandler ( connection, link, TokenProvider, endpoint, endpoint.AbsoluteUri, endpoint.AbsoluteUri, 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(link, refreshTimer); return(link); } catch { // Aborting the session will perform any necessary cleanup of // the associated link as well. session?.Abort(); throw; } }
/// <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(); }
public void CalculateRemaining(TimeSpan initialPeriod, TimeSpan elapsed, TimeSpan expectedRemaining) { Assert.That(initialPeriod.CalculateRemaining(elapsed), Is.EqualTo(expectedRemaining)); }
/// <summary> /// Creates an AMQP link for use with publishing operations. /// </summary> /// /// <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="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(AmqpConnection connection, Uri endpoint, TimeSpan timeout, 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. var authClaims = new[] { EventHubsClaim.Send }; var authExpirationUtc = await RequestAuthorizationUsingCbsAsync(connection, TokenProvider, endpoint, endpoint.AbsoluteUri, endpoint.AbsoluteUri, 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 = endpoint.AbsolutePath } }; linkSettings.AddProperty(AmqpProperty.Timeout, (uint)timeout.CalculateRemaining(stopWatch.Elapsed).TotalMilliseconds); linkSettings.AddProperty(AmqpProperty.EntityType, (int)AmqpProperty.Entity.EventHub); 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); var refreshHandler = CreateAuthorizationRefreshHandler ( connection, link, TokenProvider, endpoint, endpoint.AbsoluteUri, endpoint.AbsoluteUri, 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(link, refreshTimer); return(link); } catch { // Aborting the session will perform any necessary cleanup of // the associated link as well. session?.Abort(); throw; } }
/// <summary> /// Retrieves information about an Event Hub, including the number of partitions present /// and their identifiers. /// </summary> /// /// <param name="cancellationToken">An optional <see cref="CancellationToken"/> instance to signal the request to cancel the operation.</param> /// /// <returns>The set of information for the Event Hub that this client is associated with.</returns> /// public override async Task <EventHubProperties> GetPropertiesAsync(CancellationToken cancellationToken) { Argument.AssertNotClosed(_closed, nameof(AmqpEventHubClient)); var failedAttemptCount = 0; var retryDelay = default(TimeSpan?); var stopWatch = Stopwatch.StartNew(); try { while (!cancellationToken.IsCancellationRequested) { try { EventHubsEventSource.Log.GetPropertiesStart(EventHubName); // Create the request message and the management link. var token = await AquireAccessTokenAsync(cancellationToken).ConfigureAwait(false); using AmqpMessage request = MessageConverter.CreateEventHubPropertiesRequest(EventHubName, token); cancellationToken.ThrowIfCancellationRequested <TaskCanceledException>(); RequestResponseAmqpLink link = await ManagementLink.GetOrCreateAsync(UseMinimum(ConnectionScope.SessionTimeout, _tryTimeout.CalculateRemaining(stopWatch.Elapsed))).ConfigureAwait(false); cancellationToken.ThrowIfCancellationRequested <TaskCanceledException>(); // Send the request and wait for the response. using AmqpMessage response = await link.RequestAsync(request, _tryTimeout.CalculateRemaining(stopWatch.Elapsed)).ConfigureAwait(false); cancellationToken.ThrowIfCancellationRequested <TaskCanceledException>(); stopWatch.Stop(); // Process the response. AmqpError.ThrowIfErrorResponse(response, EventHubName); return(MessageConverter.CreateEventHubPropertiesFromResponse(response)); } catch (Exception ex) { EventHubsEventSource.Log.GetPropertiesError(EventHubName, ex.Message); // Determine if there should be a retry for the next attempt; if so enforce the delay but do not quit the loop. // Otherwise, mark the exception as active and break out of the loop. ++failedAttemptCount; retryDelay = _retryPolicy.CalculateRetryDelay(ex, failedAttemptCount); if ((retryDelay.HasValue) && (!ConnectionScope.IsDisposed)) { await Task.Delay(retryDelay.Value, cancellationToken).ConfigureAwait(false); } else { throw; } } } // If no value has been returned nor exception thrown by this point, // then cancellation has been requested. throw new TaskCanceledException(); } finally { stopWatch.Stop(); EventHubsEventSource.Log.GetPropertiesComplete(EventHubName); } }
/// <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> /// Sends an AMQP message that contains a batch of events to the associated Event Hub. If the size of events exceed the /// maximum size of a single batch, an exception will be triggered and the send will fail. /// </summary> /// /// <param name="messageFactory">A factory which can be used to produce an AMQP message containing the batch of events to be sent.</param> /// <param name="partitionKey">The hashing key to use for influencing the partition to which events should be routed.</param> /// <param name="cancellationToken">An optional <see cref="CancellationToken"/> instance to signal the request to cancel the operation.</param> /// protected virtual async Task SendAsync(Func <AmqpMessage> messageFactory, string partitionKey, CancellationToken cancellationToken) { var failedAttemptCount = 0; var logPartition = PartitionId ?? partitionKey; var retryDelay = default(TimeSpan?); var messageHash = default(string); var stopWatch = Stopwatch.StartNew(); SendingAmqpLink link; try { while (!cancellationToken.IsCancellationRequested) { try { using AmqpMessage batchMessage = messageFactory(); messageHash = batchMessage.GetHashCode().ToString(); EventHubsEventSource.Log.EventPublishStart(EventHubName, logPartition, messageHash); link = await SendLink.GetOrCreateAsync(UseMinimum(ConnectionScope.SessionTimeout, _tryTimeout)).ConfigureAwait(false); cancellationToken.ThrowIfCancellationRequested <TaskCanceledException>(); // Validate that the batch of messages 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 > MaximumMessageSize) { throw new MessageSizeExceededException(EventHubName, string.Format(Resources.MessageSizeExceeded, messageHash, batchMessage.SerializedMessageSize, MaximumMessageSize)); } // Attempt to send the message batch. var deliveryTag = new ArraySegment <byte>(BitConverter.GetBytes(Interlocked.Increment(ref _deliveryCount))); var outcome = await link.SendMessageAsync(batchMessage, deliveryTag, AmqpConstants.NullBinary, _tryTimeout.CalculateRemaining(stopWatch.Elapsed)).ConfigureAwait(false); cancellationToken.ThrowIfCancellationRequested <TaskCanceledException>(); if (outcome.DescriptorCode != Accepted.Code) { throw AmqpError.CreateExceptionForError((outcome as Rejected)?.Error, EventHubName); } // The send operation should be considered successful; return to // exit the retry loop. return; } catch (AmqpException amqpException) { throw AmqpError.CreateExceptionForError(amqpException.Error, EventHubName); } catch (Exception ex) { // 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(ex, failedAttemptCount); if ((retryDelay.HasValue) && (!ConnectionScope.IsDisposed) && (!cancellationToken.IsCancellationRequested)) { EventHubsEventSource.Log.EventPublishError(EventHubName, logPartition, messageHash, ex.Message); await Task.Delay(retryDelay.Value, cancellationToken).ConfigureAwait(false); stopWatch.Reset(); } 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) { EventHubsEventSource.Log.EventPublishError(EventHubName, logPartition, messageHash, ex.Message); throw; } finally { stopWatch.Stop(); EventHubsEventSource.Log.EventPublishComplete(EventHubName, logPartition, messageHash); } }
/// <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> /// /// </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 stopWatch = Stopwatch.StartNew(); using (AmqpMessage amqpMessage = AmqpMessageConverter.SBMessageToAmqpMessage(message)) { var request = AmqpRequestMessage.CreateRequest( ManagementConstants.Operations.ScheduleMessageOperation, timeout, null); if (_sendLink.TryGetOpenedObject(out SendingAmqpLink 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 }; RequestResponseAmqpLink mgmtLink = await _managementLink.GetOrCreateAsync( UseMinimum(_connectionScope.SessionTimeout, timeout.CalculateRemaining(stopWatch.Elapsed))) .ConfigureAwait(false); using AmqpMessage response = await mgmtLink.RequestAsync( request.AmqpMessage, timeout.CalculateRemaining(stopWatch.Elapsed)) .ConfigureAwait(false); cancellationToken.ThrowIfCancellationRequested <TaskCanceledException>(); stopWatch.Stop(); AmqpResponseMessage amqpResponseMessage = AmqpResponseMessage.CreateResponse(response); 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(); } } }