/// <summary> /// Initializes a new instance of the <see cref="AmqpProducer"/> class. /// </summary> /// /// <param name="eventHubName">The name of the Event Hub to which events will be published.</param> /// <param name="partitionId">The identifier of the Event Hub partition to which it is bound; if unbound, <c>null</c>.</param> /// <param name="connectionScope">The AMQP connection context for operations.</param> /// <param name="messageConverter">The converter to use for translating between AMQP messages and client types.</param> /// <param name="retryPolicy">The retry policy to consider when an operation fails.</param> /// <param name="requestedFeatures">The flags specifying the set of special transport features that should be opted-into.</param> /// <param name="partitionOptions">The set of options, if any, that should be considered when initializing the producer.</param> /// /// <remarks> /// As an internal type, this class performs only basic sanity checks against its arguments. It /// is assumed that callers are trusted and have performed deep validation. /// /// Any parameters passed are assumed to be owned by this instance and safe to mutate or dispose; /// creation of clones or otherwise protecting the parameters is assumed to be the purview of the /// caller. /// </remarks> /// public AmqpProducer(string eventHubName, string partitionId, AmqpConnectionScope connectionScope, AmqpMessageConverter messageConverter, EventHubsRetryPolicy retryPolicy, TransportProducerFeatures requestedFeatures = TransportProducerFeatures.None, PartitionPublishingOptions partitionOptions = null) { Argument.AssertNotNullOrEmpty(eventHubName, nameof(eventHubName)); Argument.AssertNotNull(connectionScope, nameof(connectionScope)); Argument.AssertNotNull(messageConverter, nameof(messageConverter)); Argument.AssertNotNull(retryPolicy, nameof(retryPolicy)); EventHubName = eventHubName; PartitionId = partitionId; RetryPolicy = retryPolicy; ConnectionScope = connectionScope; MessageConverter = messageConverter; ActiveFeatures = requestedFeatures; ActiveOptions = partitionOptions?.Clone() ?? new PartitionPublishingOptions(); SendLink = new FaultTolerantAmqpObject <SendingAmqpLink>( timeout => CreateLinkAndEnsureProducerStateAsync(partitionId, ActiveOptions, timeout, CancellationToken.None), link => { link.Session?.SafeClose(); link.SafeClose(); }); }
/// <summary> /// Creates a producer strongly aligned with the active protocol and transport, /// responsible for publishing <see cref="EventData" /> to the Event Hub. /// </summary> /// /// <param name="partitionId">The identifier of the partition to which the transport producer should be bound; if <c>null</c>, the producer is unbound.</param> /// <param name="requestedFeatures">The flags specifying the set of special transport features that should be opted-into.</param> /// <param name="partitionOptions">The set of options, if any, that should be considered when initializing the producer.</param> /// <param name="retryPolicy">The policy which governs retry behavior and try timeouts.</param> /// /// <returns>A <see cref="TransportProducer"/> configured in the requested manner.</returns> /// internal virtual TransportProducer CreateTransportProducer(string partitionId, TransportProducerFeatures requestedFeatures, PartitionPublishingOptions partitionOptions, EventHubsRetryPolicy retryPolicy) { Argument.AssertNotNull(retryPolicy, nameof(retryPolicy)); return(InnerClient.CreateProducer(partitionId, requestedFeatures, partitionOptions, retryPolicy)); }
public void GetPublishingOptionsOrDefaultForPartitionReturnsTheOptionsWhenThePartitionIsFound() { var partitionId = "12"; var expectedPartitionOptions = new PartitionPublishingOptions { ProducerGroupId = 1 }; var options = new EventHubProducerClientOptions(); options.PartitionOptions.Add(partitionId, expectedPartitionOptions); Assert.That(options.GetPublishingOptionsOrDefaultForPartition(partitionId), Is.SameAs(expectedPartitionOptions)); }
public void ToCoreOptionsProducesACopy() { var options = new PartitionPublishingOptions { OwnerLevel = 3, ProducerGroupId = 99, StartingSequenceNumber = 42 }; var clone = options.ToCoreOptions(); Assert.That(clone, Is.Not.Null, "The clone should not be null."); Assert.That(clone.OwnerLevel, Is.EqualTo(options.OwnerLevel), "The owner level should have been copied."); Assert.That(clone.ProducerGroupId, Is.EqualTo(options.ProducerGroupId), "The producer group identifier should have been copied."); Assert.That(clone.StartingSequenceNumber, Is.EqualTo(options.StartingSequenceNumber), "The starting sequence number should have been copied."); }
/// <summary> /// Creates a producer strongly aligned with the active protocol and transport, /// responsible for publishing <see cref="EventData" /> to the Event Hub. /// </summary> /// /// <param name="partitionId">The identifier of the partition to which the transport producer should be bound; if <c>null</c>, the producer is unbound.</param> /// <param name="requestedFeatures">The flags specifying the set of special transport features that should be opted-into.</param> /// <param name="partitionOptions">The set of options, if any, that should be considered when initializing the producer.</param> /// <param name="retryPolicy">The policy which governs retry behavior and try timeouts.</param> /// /// <returns>A <see cref="TransportProducer"/> configured in the requested manner.</returns> /// public override TransportProducer CreateProducer(string partitionId, TransportProducerFeatures requestedFeatures, PartitionPublishingOptions partitionOptions, EventHubsRetryPolicy retryPolicy) { Argument.AssertNotClosed(_closed, nameof(AmqpClient)); return(new AmqpProducer ( EventHubName, partitionId, ConnectionScope, MessageConverter, retryPolicy, requestedFeatures, partitionOptions )); }
/// <summary> /// Initializes a new instance of the <see cref="AmqpProducer"/> class. /// </summary> /// /// <param name="eventHubName">The name of the Event Hub to which events will be published.</param> /// <param name="partitionId">The identifier of the Event Hub partition to which it is bound; if unbound, <c>null</c>.</param> /// <param name="producerIdentifier">The identifier to associate with the consumer; if <c>null</c> or <see cref="string.Empty" />, a random identifier will be generated.</param> /// <param name="connectionScope">The AMQP connection context for operations.</param> /// <param name="messageConverter">The converter to use for translating between AMQP messages and client types.</param> /// <param name="retryPolicy">The retry policy to consider when an operation fails.</param> /// <param name="requestedFeatures">The flags specifying the set of special transport features that should be opted-into.</param> /// <param name="partitionOptions">The set of options, if any, that should be considered when initializing the producer.</param> /// /// <remarks> /// As an internal type, this class performs only basic sanity checks against its arguments. It /// is assumed that callers are trusted and have performed deep validation. /// /// Any parameters passed are assumed to be owned by this instance and safe to mutate or dispose; /// creation of clones or otherwise protecting the parameters is assumed to be the purview of the /// caller. /// </remarks> /// public AmqpProducer(string eventHubName, string partitionId, string producerIdentifier, AmqpConnectionScope connectionScope, AmqpMessageConverter messageConverter, EventHubsRetryPolicy retryPolicy, TransportProducerFeatures requestedFeatures = TransportProducerFeatures.None, PartitionPublishingOptions partitionOptions = null) { Argument.AssertNotNullOrEmpty(eventHubName, nameof(eventHubName)); Argument.AssertNotNull(connectionScope, nameof(connectionScope)); Argument.AssertNotNull(messageConverter, nameof(messageConverter)); Argument.AssertNotNull(retryPolicy, nameof(retryPolicy)); if (string.IsNullOrEmpty(producerIdentifier)) { producerIdentifier = Guid.NewGuid().ToString(); } EventHubName = eventHubName; PartitionId = partitionId; Identifier = producerIdentifier; RetryPolicy = retryPolicy; ConnectionScope = connectionScope; MessageConverter = messageConverter; ActiveFeatures = requestedFeatures; ActiveOptions = partitionOptions?.Clone() ?? new PartitionPublishingOptions(); SendLink = new FaultTolerantAmqpObject <SendingAmqpLink>( timeout => CreateLinkAndEnsureProducerStateAsync(partitionId, producerIdentifier, ActiveOptions, timeout, CancellationToken.None), link => { link.Session?.SafeClose(); link.SafeClose(); EventHubsEventSource.Log.FaultTolerantAmqpObjectClose(nameof(SendingAmqpLink), "", EventHubName, "", PartitionId, link.TerminalException?.Message); }); }
/// <summary> /// Creates the AMQP link to be used for producer-related operations and ensures /// that the corresponding state for the producer has been updated based on the link /// configuration. /// </summary> /// /// <param name="partitionId">The identifier of the Event Hub partition to which it is bound; if unbound, <c>null</c>.</param> /// <param name="partitionOptions">The set of options, if any, that should be considered when initializing the producer.</param> /// <param name="timeout">The timeout to apply when creating the link.</param> /// <param name="cancellationToken">The cancellation token to consider when creating the link.</param> /// /// <returns>The AMQP link to use for producer-related operations.</returns> /// /// <remarks> /// This method will modify class-level state, setting those attributes that depend on the AMQP /// link configuration. There exists a benign race condition in doing so, as there may be multiple /// concurrent callers. In this case, the attributes may be set multiple times but the resulting /// value will be the same. /// </remarks> /// protected virtual async Task <SendingAmqpLink> CreateLinkAndEnsureProducerStateAsync(string partitionId, PartitionPublishingOptions partitionOptions, TimeSpan timeout, CancellationToken cancellationToken) { var link = default(SendingAmqpLink); try { link = await ConnectionScope.OpenProducerLinkAsync(partitionId, ActiveFeatures, partitionOptions, timeout, cancellationToken).ConfigureAwait(false); if (!MaximumMessageSize.HasValue) { // This delay is necessary to prevent the link from causing issues for subsequent // operations after creating a batch. Without it, operations using the link consistently // timeout. The length of the delay does not appear significant, just the act of introducing // an asynchronous delay. // // For consistency the value used by the legacy Event Hubs client has been brought forward and // used here. await Task.Delay(15, cancellationToken).ConfigureAwait(false); MaximumMessageSize = (long)link.Settings.MaxMessageSize; } if (InitializedPartitionProperties == null) { var producerGroup = link.ExtractSettingPropertyValueOrDefault(AmqpProperty.ProducerGroupId, default(long?)); var ownerLevel = link.ExtractSettingPropertyValueOrDefault(AmqpProperty.ProducerOwnerLevel, default(short?)); var sequence = link.ExtractSettingPropertyValueOrDefault(AmqpProperty.ProducerSequenceNumber, default(int?)); // Once the properties are initialized, clear the starting sequence number to ensure that the current // sequence tracked by the service is used should the link need to be recreated; this avoids the need for // the transport producer to have awareness of the sequence numbers of events being sent. InitializedPartitionProperties = new PartitionPublishingProperties(false, producerGroup, ownerLevel, sequence); partitionOptions.StartingSequenceNumber = null; } } catch (Exception ex) { ExceptionDispatchInfo.Capture(ex.TranslateConnectionCloseDuringLinkCreationException(EventHubName)).Throw(); } return(link); }
internal override TransportProducer CreateTransportProducer(string partitionId, TransportProducerFeatures requestedFeatures, PartitionPublishingOptions partitionOptions, EventHubsRetryPolicy retryPolicy) => TransportProducerFactory();
/// <summary> /// Creates a producer strongly aligned with the active protocol and transport, /// responsible for publishing <see cref="EventData" /> to the Event Hub. /// </summary> /// /// <param name="partitionId">The identifier of the partition to which the transport producer should be bound; if <c>null</c>, the producer is unbound.</param> /// <param name="requestedFeatures">The flags specifying the set of special transport features that should be opted-into.</param> /// <param name="partitionOptions">The set of options, if any, that should be considered when initializing the producer.</param> /// <param name="retryPolicy">The policy which governs retry behavior and try timeouts.</param> /// /// <returns>A <see cref="TransportProducer"/> configured in the requested manner.</returns> /// public abstract TransportProducer CreateProducer(string partitionId, TransportProducerFeatures requestedFeatures, PartitionPublishingOptions partitionOptions, EventHubsRetryPolicy retryPolicy);
/// <summary> /// Creates the AMQP link to be used for producer-related operations and ensures /// that the corresponding state for the producer has been updated based on the link /// configuration. /// </summary> /// /// <param name="partitionId">The identifier of the Event Hub partition to which it is bound; if unbound, <c>null</c>.</param> /// <param name="partitionOptions">The set of options, if any, that should be considered when initializing the producer.</param> /// <param name="timeout">The timeout to apply when creating the link.</param> /// <param name="cancellationToken">The cancellation token to consider when creating the link.</param> /// /// <returns>The AMQP link to use for producer-related operations.</returns> /// /// <remarks> /// This method will modify class-level state, setting those attributes that depend on the AMQP /// link configuration. There exists a benign race condition in doing so, as there may be multiple /// concurrent callers. In this case, the attributes may be set multiple times but the resulting /// value will be the same. /// </remarks> /// protected virtual async Task <SendingAmqpLink> CreateLinkAndEnsureProducerStateAsync(string partitionId, PartitionPublishingOptions partitionOptions, TimeSpan timeout, CancellationToken cancellationToken) { var link = default(SendingAmqpLink); try { link = await ConnectionScope.OpenProducerLinkAsync(partitionId, timeout, cancellationToken).ConfigureAwait(false); if (!MaximumMessageSize.HasValue) { // This delay is necessary to prevent the link from causing issues for subsequent // operations after creating a batch. Without it, operations using the link consistently // timeout. The length of the delay does not appear significant, just the act of introducing // an asynchronous delay. // // For consistency the value used by the legacy Event Hubs client has been brought forward and // used here. await Task.Delay(15, cancellationToken).ConfigureAwait(false); MaximumMessageSize = (long)link.Settings.MaxMessageSize; } if (InitializedPartitionProperties == null) { if ((ActiveFeatures & TransportProducerFeatures.IdempotentPublishing) != 0) { throw new NotImplementedException(nameof(CreateLinkAndEnsureProducerStateAsync)); } else { InitializedPartitionProperties = new PartitionPublishingProperties(false, null, null, null); } } } catch (Exception ex) { ExceptionDispatchInfo.Capture(ex.TranslateConnectionCloseDuringLinkCreationException(EventHubName)).Throw(); } return(link); }