/// <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> /// Sends a set of messages to the associated Queue/Topic using a batched approach. /// </summary> /// /// <param name="messageBatch"></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( ServiceBusMessageBatch messageBatch, TimeSpan timeout, CancellationToken cancellationToken) { var stopWatch = Stopwatch.StartNew(); AmqpMessage messageFactory() => AmqpMessageConverter.BatchSBMessagesAsAmqpMessage(messageBatch.AsEnumerable <ServiceBusMessage>()); using (AmqpMessage batchMessage = messageFactory()) { //ServiceBusEventSource.Log.SendStart(Entityname, messageHash); string messageHash = batchMessage.GetHashCode().ToString(); 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 > MaximumMessageSize) { throw new ServiceBusException(string.Format(Resources1.MessageSizeExceeded, messageHash, batchMessage.SerializedMessageSize, MaximumMessageSize, _entityName), ServiceBusException.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, timeout.CalculateRemaining(stopWatch.Elapsed)).ConfigureAwait(false); cancellationToken.ThrowIfCancellationRequested <TaskCanceledException>(); if (outcome.DescriptorCode != Accepted.Code) { throw AmqpError.CreateExceptionForError((outcome as Rejected)?.Error, _entityName); } //ServiceBusEventSource.Log.SendStop(Entityname, messageHash); cancellationToken.ThrowIfCancellationRequested <TaskCanceledException>(); stopWatch.Stop(); } }
/// <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); } }