/// <summary> /// Reads the set of partition publishing properties active for this producer at the time it was initialized. /// </summary> /// /// <param name="cancellationToken">The cancellation token to consider when creating the link.</param> /// /// <returns>The set of <see cref="PartitionPublishingProperties" /> observed when the producer was initialized.</returns> /// /// <remarks> /// It is important to note that these properties are a snapshot of the service state at the time when the /// producer was initialized; they do not necessarily represent the current state of the service. /// </remarks> /// public override async ValueTask <PartitionPublishingProperties> ReadInitializationPublishingPropertiesAsync(CancellationToken cancellationToken) { Argument.AssertNotClosed(_closed, nameof(AmqpProducer)); Argument.AssertNotClosed(ConnectionScope.IsDisposed, nameof(EventHubConnection)); // If the properties were already initialized, use them. if (InitializedPartitionProperties != null) { return(InitializedPartitionProperties); } // Initialize the properties by forcing the link to be opened. var failedAttemptCount = 0; var tryTimeout = RetryPolicy.CalculateTryTimeout(0); while ((!cancellationToken.IsCancellationRequested) && (!_closed)) { try { if (!SendLink.TryGetOpenedObject(out _)) { await SendLink.GetOrCreateAsync(UseMinimum(ConnectionScope.SessionTimeout, tryTimeout), cancellationToken).ConfigureAwait(false); } break; } catch (Exception ex) { ++failedAttemptCount; // 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. var activeEx = ex.TranslateServiceException(EventHubName); var retryDelay = RetryPolicy.CalculateRetryDelay(activeEx, failedAttemptCount); if ((retryDelay.HasValue) && (!ConnectionScope.IsDisposed) && (!cancellationToken.IsCancellationRequested)) { await Task.Delay(retryDelay.Value, cancellationToken).ConfigureAwait(false); tryTimeout = RetryPolicy.CalculateTryTimeout(failedAttemptCount); } else if (ex is AmqpException) { ExceptionDispatchInfo.Capture(activeEx).Throw(); } else { throw; } } } cancellationToken.ThrowIfCancellationRequested <TaskCanceledException>(); return(InitializedPartitionProperties); }
/// <summary> /// Creates a size-constraint batch to which <see cref="EventData" /> may be added using a try-based pattern. If an event would /// exceed the maximum allowable size of the batch, the batch will not allow adding the event and signal that scenario using its /// return value. /// /// Because events that would violate the size constraint cannot be added, publishing a batch will not trigger an exception when /// attempting to send the events to the Event Hubs service. /// </summary> /// /// <param name="options">The set of options to consider when creating this batch.</param> /// <param name="cancellationToken">An optional <see cref="CancellationToken"/> instance to signal the request to cancel the operation.</param> /// /// <returns>An <see cref="EventDataBatch" /> with the requested <paramref name="options"/>.</returns> /// public override async ValueTask <TransportEventBatch> CreateBatchAsync(CreateBatchOptions options, CancellationToken cancellationToken) { Argument.AssertNotNull(options, nameof(options)); // Ensure that maximum message size has been determined; this depends on the underlying // AMQP link, so if not set, requesting the link will ensure that it is populated. if (!MaximumMessageSize.HasValue) { await SendLink.GetOrCreateAsync(RetryPolicy.CalculateTryTimeout(0)).ConfigureAwait(false); } // Ensure that there was a maximum size populated; if none was provided, // default to the maximum size allowed by the link. options.MaximumSizeInBytes ??= MaximumMessageSize; Argument.AssertInRange(options.MaximumSizeInBytes.Value, EventHubProducerClient.MinimumBatchSizeLimit, MaximumMessageSize.Value, nameof(options.MaximumSizeInBytes)); return(new AmqpEventBatch(MessageConverter, options)); }
/// <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 { var tryTimeout = RetryPolicy.CalculateTryTimeout(0); 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 EventHubsException(EventHubName, string.Format(Resources.MessageSizeExceeded, messageHash, batchMessage.SerializedMessageSize, MaximumMessageSize), EventHubsException.FailureReason.MessageSizeExceeded); } // 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 (Exception ex) { Exception activeEx = ex.TranslateServiceException(EventHubName); // Determine if there should be a retry for the next attempt; if so enforce the delay but do not quit the loop. // Otherwise, bubble the exception. ++failedAttemptCount; retryDelay = RetryPolicy.CalculateRetryDelay(activeEx, failedAttemptCount); if ((retryDelay.HasValue) && (!ConnectionScope.IsDisposed) && (!cancellationToken.IsCancellationRequested)) { EventHubsEventSource.Log.EventPublishError(EventHubName, logPartition, messageHash, activeEx.Message); await Task.Delay(retryDelay.Value, cancellationToken).ConfigureAwait(false); tryTimeout = RetryPolicy.CalculateTryTimeout(failedAttemptCount); stopWatch.Reset(); } else if (ex is AmqpException) { throw activeEx; } else { throw; } } } // If no value has been returned nor exception thrown by this point, // then cancellation has been requested. throw new TaskCanceledException(); } catch (Exception ex) { EventHubsEventSource.Log.EventPublishError(EventHubName, logPartition, messageHash, ex.Message); throw; } finally { stopWatch.Stop(); EventHubsEventSource.Log.EventPublishComplete(EventHubName, logPartition, messageHash); } }
/// <summary> /// Creates a size-constraint batch to which <see cref="EventData" /> may be added using a try-based pattern. If an event would /// exceed the maximum allowable size of the batch, the batch will not allow adding the event and signal that scenario using its /// return value. /// /// Because events that would violate the size constraint cannot be added, publishing a batch will not trigger an exception when /// attempting to send the events to the Event Hubs service. /// </summary> /// /// <param name="options">The set of options to consider when creating this batch.</param> /// <param name="cancellationToken">An optional <see cref="CancellationToken"/> instance to signal the request to cancel the operation.</param> /// /// <returns>An <see cref="EventDataBatch" /> with the requested <paramref name="options"/>.</returns> /// public override async ValueTask <TransportEventBatch> CreateBatchAsync(CreateBatchOptions options, CancellationToken cancellationToken) { Argument.AssertNotNull(options, nameof(options)); Argument.AssertNotClosed(_closed, nameof(AmqpProducer)); cancellationToken.ThrowIfCancellationRequested <TaskCanceledException>(); // Ensure that maximum message size has been determined; this depends on the underlying // AMQP link, so if not set, requesting the link will ensure that it is populated. if (!MaximumMessageSize.HasValue) { var failedAttemptCount = 0; var retryDelay = default(TimeSpan?); var tryTimeout = RetryPolicy.CalculateTryTimeout(0); while (!cancellationToken.IsCancellationRequested) { try { await SendLink.GetOrCreateAsync(UseMinimum(ConnectionScope.SessionTimeout, tryTimeout)).ConfigureAwait(false); break; } catch (Exception ex) { Exception activeEx = ex.TranslateServiceException(EventHubName); // Determine if there should be a retry for the next attempt; if so enforce the delay but do not quit the loop. // Otherwise, bubble the exception. ++failedAttemptCount; retryDelay = RetryPolicy.CalculateRetryDelay(activeEx, failedAttemptCount); if ((retryDelay.HasValue) && (!ConnectionScope.IsDisposed) && (!cancellationToken.IsCancellationRequested)) { await Task.Delay(retryDelay.Value, cancellationToken).ConfigureAwait(false); tryTimeout = RetryPolicy.CalculateTryTimeout(failedAttemptCount); } else if (ex is AmqpException) { throw activeEx; } else { throw; } } } // If MaximumMessageSize has not been populated nor exception thrown // by this point, then cancellation has been requested. if (!MaximumMessageSize.HasValue) { throw new TaskCanceledException(); } } // Ensure that there was a maximum size populated; if none was provided, // default to the maximum size allowed by the link. options.MaximumSizeInBytes ??= MaximumMessageSize; Argument.AssertInRange(options.MaximumSizeInBytes.Value, EventHubProducerClient.MinimumBatchSizeLimit, MaximumMessageSize.Value, nameof(options.MaximumSizeInBytes)); return(new AmqpEventBatch(MessageConverter, options)); }
/// <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="messages">The set of AMQP messages to packaged in a batch envelope and 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> /// /// <remarks> /// Callers retain ownership of the <paramref name="messages" /> passed and hold responsibility for /// ensuring that they are disposed. /// </remarks> /// protected virtual async Task SendAsync(IReadOnlyCollection <AmqpMessage> messages, string partitionKey, CancellationToken cancellationToken) { var failedAttemptCount = 0; var logPartition = PartitionId ?? partitionKey; var operationId = Guid.NewGuid().ToString("D", CultureInfo.InvariantCulture); TimeSpan? retryDelay; SendingAmqpLink link; try { var tryTimeout = RetryPolicy.CalculateTryTimeout(0); while (!cancellationToken.IsCancellationRequested) { EventHubsEventSource.Log.EventPublishStart(EventHubName, logPartition, operationId); try { using AmqpMessage batchMessage = MessageConverter.CreateBatchFromMessages(messages); if (!SendLink.TryGetOpenedObject(out link)) { link = await SendLink.GetOrCreateAsync(UseMinimum(ConnectionScope.SessionTimeout, tryTimeout), cancellationToken).ConfigureAwait(false); } // 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 EventHubsException(EventHubName, string.Format(CultureInfo.CurrentCulture, Resources.MessageSizeExceeded, operationId, batchMessage.SerializedMessageSize, MaximumMessageSize), EventHubsException.FailureReason.MessageSizeExceeded); } // 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, cancellationToken).ConfigureAwait(false); 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 (Exception ex) { Exception activeEx = ex.TranslateServiceException(EventHubName); // Determine if there should be a retry for the next attempt; if so enforce the delay but do not quit the loop. // Otherwise, bubble the exception. ++failedAttemptCount; retryDelay = RetryPolicy.CalculateRetryDelay(activeEx, failedAttemptCount); if ((retryDelay.HasValue) && (!ConnectionScope.IsDisposed) && (!_closed) && (!cancellationToken.IsCancellationRequested)) { EventHubsEventSource.Log.EventPublishError(EventHubName, logPartition, operationId, activeEx.Message); await Task.Delay(retryDelay.Value, cancellationToken).ConfigureAwait(false); tryTimeout = RetryPolicy.CalculateTryTimeout(failedAttemptCount); } else if (ex is AmqpException) { ExceptionDispatchInfo.Capture(activeEx).Throw(); } else { throw; } } } // If no value has been returned nor exception thrown by this point, // then cancellation has been requested. throw new TaskCanceledException(); } catch (TaskCanceledException) { throw; } catch (Exception ex) { EventHubsEventSource.Log.EventPublishError(EventHubName, logPartition, operationId, ex.Message); throw; } finally { EventHubsEventSource.Log.EventPublishComplete(EventHubName, logPartition, operationId, failedAttemptCount); } }