diff --git a/sdk/core/Azure.Core/src/Pipeline/RetryMode.cs b/sdk/core/Azure.Core/src/Pipeline/RetryMode.cs
index 5f73c60ddc2ee..3235e956fd460 100644
--- a/sdk/core/Azure.Core/src/Pipeline/RetryMode.cs
+++ b/sdk/core/Azure.Core/src/Pipeline/RetryMode.cs
@@ -3,9 +3,21 @@
namespace Azure.Core.Pipeline
{
+ ///
+ /// The type of approach to apply when calculating the delay
+ /// between retry attempts.
+ ///
public enum RetryMode
{
+ ///
+ /// Retry attempts happen at fixed intervals; each delay is a consistent duration.
+ ///
Fixed,
+
+ ///
+ /// Retry attempts will delay based on a backoff strategy, where each attempt will increase
+ /// the duration that it waits before retrying.
+ ///
Exponential
}
}
diff --git a/sdk/core/Azure.Core/src/Pipeline/RetryOptions.cs b/sdk/core/Azure.Core/src/Pipeline/RetryOptions.cs
index 7ccb6d1686ac4..a75bc828139e0 100644
--- a/sdk/core/Azure.Core/src/Pipeline/RetryOptions.cs
+++ b/sdk/core/Azure.Core/src/Pipeline/RetryOptions.cs
@@ -5,25 +5,30 @@
namespace Azure.Core.Pipeline
{
+ ///
+ /// The set of options that can be specified to influence how
+ /// retry attempts are made, and a failure is eligible to be retried.
+ ///
public class RetryOptions
{
///
- /// Gets or sets the maximum number of retry attempts before giving up.
+ /// The maximum number of retry attempts before giving up.
///
public int MaxRetries { get; set; } = 3;
///
- /// Gets or sets the timespan used as delay between the retries or as a base for exponential backoff.
+ /// The delay between retry attempts for a fixed approach or the delay
+ /// on which to base calculations for a backoff-based approach.
///
public TimeSpan Delay { get; set; } = TimeSpan.FromSeconds(0.8);
///
- /// Gets or sets maximum timespan to pause between requests.
+ /// The maximum permissible delay between retry attempts.
///
public TimeSpan MaxDelay { get; set; } = TimeSpan.FromMinutes(1);
///
- /// Gets os sets retry mode
+ /// The approach to use for calculating retry delays.
///
public RetryMode Mode { get; set; } = RetryMode.Exponential;
}
diff --git a/sdk/eventhub/Azure.Messaging.EventHubs/samples/Sample2_ClientWithCustomOptions.cs b/sdk/eventhub/Azure.Messaging.EventHubs/samples/Sample2_ClientWithCustomOptions.cs
index 09b2e21dee3a6..6df4cd431b891 100755
--- a/sdk/eventhub/Azure.Messaging.EventHubs/samples/Sample2_ClientWithCustomOptions.cs
+++ b/sdk/eventhub/Azure.Messaging.EventHubs/samples/Sample2_ClientWithCustomOptions.cs
@@ -53,12 +53,13 @@ public async Task RunAsync(string connectionString,
var clientOptions = new EventHubClientOptions
{
- DefaultTimeout = TimeSpan.FromMinutes(1),
- Retry = new ExponentialRetry(TimeSpan.FromSeconds(0.25), TimeSpan.FromSeconds(30), 5),
TransportType = TransportType.AmqpWebSockets,
Proxy = (IWebProxy)null
};
+ clientOptions.RetryOptions.MaximumRetries = 5;
+ clientOptions.RetryOptions.TryTimeout = TimeSpan.FromMinutes(1);
+
await using (var client = new EventHubClient(connectionString, eventHubName, clientOptions))
{
// Using the client, we will inspect the Event Hub that it is connected to, getting
diff --git a/sdk/eventhub/Azure.Messaging.EventHubs/src/Azure.Messaging.EventHubs.csproj b/sdk/eventhub/Azure.Messaging.EventHubs/src/Azure.Messaging.EventHubs.csproj
index 9c27aab411379..d7318f2f015f4 100755
--- a/sdk/eventhub/Azure.Messaging.EventHubs/src/Azure.Messaging.EventHubs.csproj
+++ b/sdk/eventhub/Azure.Messaging.EventHubs/src/Azure.Messaging.EventHubs.csproj
@@ -39,7 +39,6 @@
-
diff --git a/sdk/eventhub/Azure.Messaging.EventHubs/src/Compatibility/TrackOneEventHubClient.cs b/sdk/eventhub/Azure.Messaging.EventHubs/src/Compatibility/TrackOneEventHubClient.cs
index 591b511e30a7e..9674f48369093 100755
--- a/sdk/eventhub/Azure.Messaging.EventHubs/src/Compatibility/TrackOneEventHubClient.cs
+++ b/sdk/eventhub/Azure.Messaging.EventHubs/src/Compatibility/TrackOneEventHubClient.cs
@@ -23,6 +23,9 @@ namespace Azure.Messaging.EventHubs.Compatibility
///
internal sealed class TrackOneEventHubClient : TransportEventHubClient
{
+ /// The active retry policy for the client.
+ private EventHubRetryPolicy _retryPolicy;
+
/// A lazy instantiation of the client instance to delegate operation to.
private Lazy _trackOneClient;
@@ -40,6 +43,7 @@ internal sealed class TrackOneEventHubClient : TransportEventHubClient
/// The path of the specific Event Hub to connect the client to.
/// The Azure managed identity credential to use for authorization. Access controls may be specified by the Event Hubs namespace or the requeseted Event Hub, depending on Azure configuration.
/// A set of options to apply when configuring the client.
+ /// The default retry policy to use if no retry options were specified in the .
///
///
/// As an internal type, this class performs only basic sanity checks against its arguments. It
@@ -53,7 +57,8 @@ internal sealed class TrackOneEventHubClient : TransportEventHubClient
public TrackOneEventHubClient(string host,
string eventHubPath,
TokenCredential credential,
- EventHubClientOptions clientOptions) : this(host, eventHubPath, credential, clientOptions, CreateClient)
+ EventHubClientOptions clientOptions,
+ EventHubRetryPolicy defaultRetryPolicy) : this(host, eventHubPath, credential, clientOptions, defaultRetryPolicy, CreateClient)
{
}
@@ -65,6 +70,7 @@ public TrackOneEventHubClient(string host,
/// The path of the specific Event Hub to connect the client to.
/// The Azure managed identity credential to use for authorization. Access controls may be specified by the Event Hubs namespace or the requeseted Event Hub, depending on Azure configuration.
/// A set of options to apply when configuring the client.
+ /// The default retry policy to use if no retry options were specified in the .
/// A delegate that can be used for creation of the to which operations are delegated to.
///
///
@@ -76,18 +82,21 @@ public TrackOneEventHubClient(string host,
/// caller.
///
///
- internal TrackOneEventHubClient(string host,
- string eventHubPath,
- TokenCredential credential,
- EventHubClientOptions clientOptions,
- Func eventHubClientFactory)
+ public TrackOneEventHubClient(string host,
+ string eventHubPath,
+ TokenCredential credential,
+ EventHubClientOptions clientOptions,
+ EventHubRetryPolicy defaultRetryPolicy,
+ Func, TrackOne.EventHubClient> eventHubClientFactory)
{
Guard.ArgumentNotNullOrEmpty(nameof(host), host);
Guard.ArgumentNotNullOrEmpty(nameof(eventHubPath), eventHubPath);
Guard.ArgumentNotNull(nameof(credential), credential);
Guard.ArgumentNotNull(nameof(clientOptions), clientOptions);
+ Guard.ArgumentNotNull(nameof(defaultRetryPolicy), defaultRetryPolicy);
- _trackOneClient = new Lazy(() => eventHubClientFactory(host, eventHubPath, credential, clientOptions), LazyThreadSafetyMode.PublicationOnly);
+ _retryPolicy = defaultRetryPolicy;
+ _trackOneClient = new Lazy(() => eventHubClientFactory(host, eventHubPath, credential, clientOptions, () => _retryPolicy), LazyThreadSafetyMode.PublicationOnly);
}
///
@@ -99,13 +108,15 @@ internal TrackOneEventHubClient(string host,
/// The path of the specific Event Hub to connect the client to.
/// The Azure managed identity credential to use for authorization. Access controls may be specified by the Event Hubs namespace or the requeseted Event Hub, depending on Azure configuration.
/// A set of options to apply when configuring the client.
+ /// A function that retrieves a default retry policy to use if no retry options were specified in the .
///
/// The to use.
///
- internal static TrackOne.EventHubClient CreateClient(string host,
- string eventHubPath,
- TokenCredential credential,
- EventHubClientOptions clientOptions)
+ public static TrackOne.EventHubClient CreateClient(string host,
+ string eventHubPath,
+ TokenCredential credential,
+ EventHubClientOptions clientOptions,
+ Func defaultRetryPolicyFactory)
{
// Translate the connection type into the corresponding Track One transport type.
@@ -155,12 +166,34 @@ internal static TrackOne.EventHubClient CreateClient(string host,
// Build and configure the client.
- var client = TrackOne.EventHubClient.Create(endpointBuilder.Uri, eventHubPath, tokenProvider, clientOptions.DefaultTimeout, transportType);
+ var retryPolicy = (clientOptions.RetryOptions != null)
+ ? new BasicRetryPolicy(clientOptions.RetryOptions)
+ : defaultRetryPolicyFactory();
+
+ var client = TrackOne.EventHubClient.Create(endpointBuilder.Uri, eventHubPath, tokenProvider, retryPolicy.CalculateTryTimeout(0), transportType);
client.WebProxy = clientOptions.Proxy;
+ client.RetryPolicy = new TrackOneRetryPolicy(retryPolicy);
return client;
}
+ ///
+ /// Updates the active retry policy for the client.
+ ///
+ ///
+ /// The retry policy to set as active.
+ ///
+ public override void UpdateRetryPolicy(EventHubRetryPolicy newRetryPolicy)
+ {
+ _retryPolicy = newRetryPolicy;
+
+ if (_trackOneClient.IsValueCreated)
+ {
+ TrackOneClient.RetryPolicy = new TrackOneRetryPolicy(newRetryPolicy);
+ TrackOneClient.ConnectionStringBuilder.OperationTimeout = newRetryPolicy.CalculateTryTimeout(0);
+ }
+ }
+
///
/// Retrieves information about an Event Hub, including the number of partitions present
/// and their identifiers.
@@ -236,26 +269,31 @@ public override async Task GetPartitionPropertiesAsync(stri
///
///
/// The set of options to apply when creating the producer.
+ /// The default retry policy to use if no retry options were specified in the .
///
/// An Event Hub producer configured in the requested manner.
///
- public override EventHubProducer CreateProducer(EventHubProducerOptions producerOptions)
+ public override EventHubProducer CreateProducer(EventHubProducerOptions producerOptions,
+ EventHubRetryPolicy defaultRetryPolicy)
{
- TrackOne.EventDataSender CreateSenderFactory()
+ TrackOne.EventDataSender CreateSenderFactory(EventHubRetryPolicy activeRetryPolicy)
{
var producer = TrackOneClient.CreateEventSender(producerOptions.PartitionId);
-
- (TimeSpan minBackoff, TimeSpan maxBackoff, int maxRetries) = ((ExponentialRetry)producerOptions.Retry).GetProperties();
- producer.RetryPolicy = new RetryExponential(minBackoff, maxBackoff, maxRetries);
+ producer.RetryPolicy = new TrackOneRetryPolicy(activeRetryPolicy);
return producer;
}
+ var initialRetryPolicy = (producerOptions.RetryOptions != null)
+ ? new BasicRetryPolicy(producerOptions.RetryOptions)
+ : defaultRetryPolicy;
+
return new EventHubProducer
(
- new TrackOneEventHubProducer(CreateSenderFactory),
+ new TrackOneEventHubProducer(CreateSenderFactory, initialRetryPolicy),
TrackOneClient.EventHubName,
- producerOptions
+ producerOptions,
+ initialRetryPolicy
);
}
@@ -279,15 +317,17 @@ TrackOne.EventDataSender CreateSenderFactory()
/// The identifier of the Event Hub partition from which events will be received.
/// The position within the partition where the consumer should begin reading events.
/// The set of options to apply when creating the consumer.
+ /// The default retry policy to use if no retry options were specified in the .
///
/// An Event Hub consumer configured in the requested manner.
///
public override EventHubConsumer CreateConsumer(string consumerGroup,
string partitionId,
EventPosition eventPosition,
- EventHubConsumerOptions consumerOptions)
+ EventHubConsumerOptions consumerOptions,
+ EventHubRetryPolicy defaultRetryPolicy)
{
- TrackOne.PartitionReceiver CreateReceiverFactory()
+ TrackOne.PartitionReceiver CreateReceiverFactory(EventHubRetryPolicy activeRetryPolicy)
{
var position = new TrackOne.EventPosition
{
@@ -297,7 +337,10 @@ TrackOne.PartitionReceiver CreateReceiverFactory()
EnqueuedTimeUtc = eventPosition.EnqueuedTime?.UtcDateTime
};
- var trackOneOptions = new TrackOne.ReceiverOptions { Identifier = consumerOptions.Identifier };
+ var trackOneOptions = new TrackOne.ReceiverOptions
+ {
+ Identifier = consumerOptions.Identifier
+ };
PartitionReceiver consumer;
@@ -310,20 +353,24 @@ TrackOne.PartitionReceiver CreateReceiverFactory()
consumer = TrackOneClient.CreateReceiver(consumerGroup, partitionId, position, trackOneOptions);
}
- (TimeSpan minBackoff, TimeSpan maxBackoff, int maxRetries) = ((ExponentialRetry)consumerOptions.Retry).GetProperties();
- consumer.RetryPolicy = new RetryExponential(minBackoff, maxBackoff, maxRetries);
+ consumer.RetryPolicy = new TrackOneRetryPolicy(activeRetryPolicy);
return consumer;
}
+ var initialRetryPolicy = (consumerOptions.RetryOptions != null)
+ ? new BasicRetryPolicy(consumerOptions.RetryOptions)
+ : defaultRetryPolicy;
+
return new EventHubConsumer
(
- new TrackOneEventHubConsumer(CreateReceiverFactory),
+ new TrackOneEventHubConsumer(CreateReceiverFactory, initialRetryPolicy),
TrackOneClient.EventHubName,
partitionId,
consumerGroup,
eventPosition,
- consumerOptions
+ consumerOptions,
+ initialRetryPolicy
);
}
diff --git a/sdk/eventhub/Azure.Messaging.EventHubs/src/Compatibility/TrackOneEventHubConsumer.cs b/sdk/eventhub/Azure.Messaging.EventHubs/src/Compatibility/TrackOneEventHubConsumer.cs
index 3267138e9fa80..0a590cf07ff89 100755
--- a/sdk/eventhub/Azure.Messaging.EventHubs/src/Compatibility/TrackOneEventHubConsumer.cs
+++ b/sdk/eventhub/Azure.Messaging.EventHubs/src/Compatibility/TrackOneEventHubConsumer.cs
@@ -20,6 +20,9 @@ namespace Azure.Messaging.EventHubs.Compatibility
///
internal sealed class TrackOneEventHubConsumer : TransportEventHubConsumer
{
+ /// The active retry policy for the producer.
+ private EventHubRetryPolicy _retryPolicy;
+
/// A lazy instantiation of the producer instance to delegate operation to.
private Lazy _trackOneReceiver;
@@ -34,6 +37,7 @@ internal sealed class TrackOneEventHubConsumer : TransportEventHubConsumer
///
///
/// A delegate that can be used for creation of the to which operations are delegated to.
+ /// The retry policy to use when creating the .
///
///
/// As an internal type, this class performs only basic sanity checks against its arguments. It
@@ -44,10 +48,30 @@ internal sealed class TrackOneEventHubConsumer : TransportEventHubConsumer
/// caller.
///
///
- public TrackOneEventHubConsumer(Func trackOneReceiverFactory)
+ public TrackOneEventHubConsumer(Func trackOneReceiverFactory,
+ EventHubRetryPolicy retryPolicy)
{
Guard.ArgumentNotNull(nameof(trackOneReceiverFactory), trackOneReceiverFactory);
- _trackOneReceiver = new Lazy(trackOneReceiverFactory, LazyThreadSafetyMode.PublicationOnly);
+ Guard.ArgumentNotNull(nameof(retryPolicy), retryPolicy);
+
+ _retryPolicy = retryPolicy;
+ _trackOneReceiver = new Lazy(() => trackOneReceiverFactory(_retryPolicy), LazyThreadSafetyMode.PublicationOnly);
+ }
+
+ ///
+ /// Updates the active retry policy for the client.
+ ///
+ ///
+ /// The retry policy to set as active.
+ ///
+ public override void UpdateRetryPolicy(EventHubRetryPolicy newRetryPolicy)
+ {
+ _retryPolicy = newRetryPolicy;
+
+ if (_trackOneReceiver.IsValueCreated)
+ {
+ TrackOneReceiver.RetryPolicy = new TrackOneRetryPolicy(newRetryPolicy);
+ }
}
///
diff --git a/sdk/eventhub/Azure.Messaging.EventHubs/src/Compatibility/TrackOneEventHubProducer.cs b/sdk/eventhub/Azure.Messaging.EventHubs/src/Compatibility/TrackOneEventHubProducer.cs
index d2e3e6d424f67..78602a91da8a1 100755
--- a/sdk/eventhub/Azure.Messaging.EventHubs/src/Compatibility/TrackOneEventHubProducer.cs
+++ b/sdk/eventhub/Azure.Messaging.EventHubs/src/Compatibility/TrackOneEventHubProducer.cs
@@ -20,6 +20,9 @@ namespace Azure.Messaging.EventHubs.Compatibility
///
internal sealed class TrackOneEventHubProducer : TransportEventHubProducer
{
+ /// The active retry policy for the producer.
+ private EventHubRetryPolicy _retryPolicy;
+
/// A lazy instantiation of the producer instance to delegate operation to.
private Lazy _trackOneSender;
@@ -34,6 +37,7 @@ internal sealed class TrackOneEventHubProducer : TransportEventHubProducer
///
///
/// A delegate that can be used for creation of the to which operations are delegated to.
+ /// The retry policy to use when creating the .
///
///
/// As an internal type, this class performs only basic sanity checks against its arguments. It
@@ -44,10 +48,30 @@ internal sealed class TrackOneEventHubProducer : TransportEventHubProducer
/// caller.
///
///
- public TrackOneEventHubProducer(Func trackOneSenderFactory)
+ public TrackOneEventHubProducer(Func trackOneSenderFactory,
+ EventHubRetryPolicy retryPolicy)
{
Guard.ArgumentNotNull(nameof(trackOneSenderFactory), trackOneSenderFactory);
- _trackOneSender = new Lazy(trackOneSenderFactory, LazyThreadSafetyMode.PublicationOnly);
+ Guard.ArgumentNotNull(nameof(retryPolicy), retryPolicy);
+
+ _retryPolicy = retryPolicy;
+ _trackOneSender = new Lazy(() => trackOneSenderFactory(_retryPolicy), LazyThreadSafetyMode.PublicationOnly);
+ }
+
+ ///
+ /// Updates the active retry policy for the client.
+ ///
+ ///
+ /// The retry policy to set as active.
+ ///
+ public override void UpdateRetryPolicy(EventHubRetryPolicy newRetryPolicy)
+ {
+ _retryPolicy = newRetryPolicy;
+
+ if (_trackOneSender.IsValueCreated)
+ {
+ TrackOneSender.RetryPolicy = new TrackOneRetryPolicy(newRetryPolicy);
+ }
}
///
@@ -71,7 +95,7 @@ static TrackOne.EventData TransformEvent(EventData eventData) =>
try
{
- await TrackOneSender.SendAsync(events.Select(TransformEvent), sendOptions?.PartitionKey);
+ await TrackOneSender.SendAsync(events.Select(TransformEvent), sendOptions?.PartitionKey).ConfigureAwait(false);
}
catch (TrackOne.EventHubsException ex)
{
diff --git a/sdk/eventhub/Azure.Messaging.EventHubs/src/Compatibility/TrackOneRetryPolicy.cs b/sdk/eventhub/Azure.Messaging.EventHubs/src/Compatibility/TrackOneRetryPolicy.cs
new file mode 100755
index 0000000000000..d727ea83b0838
--- /dev/null
+++ b/sdk/eventhub/Azure.Messaging.EventHubs/src/Compatibility/TrackOneRetryPolicy.cs
@@ -0,0 +1,98 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+
+using System;
+using Azure.Messaging.EventHubs.Core;
+using TrackOne;
+
+namespace Azure.Messaging.EventHubs.Compatibility
+{
+ ///
+ /// A retry policy that conforms to the track one API shape, which delegates the retry calculations
+ /// to a modern track two policy implementation.
+ ///
+ ///
+ ///
+ ///
+ internal sealed class TrackOneRetryPolicy : TrackOne.RetryPolicy
+ {
+ /// The modern retry policy to use as the source of retry calculations.
+ private EventHubRetryPolicy _sourcePolicy;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ ///
+ /// The modern retry policy to use as the source of retry calculations.
+ ///
+ public TrackOneRetryPolicy(EventHubRetryPolicy sourcePolicy) : base()
+ {
+ Guard.ArgumentNotNull(nameof(sourcePolicy), sourcePolicy);
+ _sourcePolicy = sourcePolicy;
+ }
+
+ ///
+ /// Invoked by the base implementation to determine the delay to wait until the next
+ /// retry attempt.
+ ///
+ ///
+ /// The last exception that was encountered; may be null if the retry was not triggered by an exception.
+ /// The remaining time for the overall operation timeout.
+ /// The base wait time for retries, in seconds.
+ /// The number of retry attempts that have been made.
+ ///
+ /// The duration to wait before retrying; or null if the operation should not be retried.
+ ///
+ protected override TimeSpan? OnGetNextRetryInterval(Exception lastException,
+ TimeSpan remainingTime,
+ int baseWaitTimeSecs,
+ int retryCount)
+ {
+ // If there is no time remaining, do not retry.
+
+ if (remainingTime <= TimeSpan.Zero)
+ {
+ return null;
+ }
+
+ // Delegate to the track one code for eligibility to retry an exception, for
+ // the sake of consistency.
+
+ if (!TrackOne.RetryPolicy.IsRetryableException(lastException))
+ {
+ return null;
+ }
+
+ Exception mappedException;
+
+ switch (lastException)
+ {
+ case TrackOne.EventHubsException ex:
+ mappedException = ex.MapToTrackTwoException();
+ break;
+
+ default:
+ mappedException = lastException;
+ break;
+ }
+
+ var delay = _sourcePolicy.CalculateRetryDelay(mappedException, retryCount);
+
+ if (delay > remainingTime)
+ {
+ return null;
+ }
+
+ return delay;
+ }
+
+ ///
+ /// Creates a new copy of the current and clones it into a new instance.
+ ///
+ ///
+ /// A new copy of .
+ ///
+ public override RetryPolicy Clone() =>
+ new TrackOneRetryPolicy(_sourcePolicy);
+ }
+}
diff --git a/sdk/eventhub/Azure.Messaging.EventHubs/src/Core/BasicRetryPolicy.cs b/sdk/eventhub/Azure.Messaging.EventHubs/src/Core/BasicRetryPolicy.cs
new file mode 100755
index 0000000000000..4f4a708a71d30
--- /dev/null
+++ b/sdk/eventhub/Azure.Messaging.EventHubs/src/Core/BasicRetryPolicy.cs
@@ -0,0 +1,181 @@
+using System;
+using System.Globalization;
+using System.Net.Sockets;
+using System.Threading;
+using System.Threading.Tasks;
+using Azure.Messaging.EventHubs.Errors;
+
+namespace Azure.Messaging.EventHubs.Core
+{
+ ///
+ /// The default retry policy for the Event Hubs client library, respecting the
+ /// configuration specified as a set of .
+ ///
+ ///
+ ///
+ ///
+ internal class BasicRetryPolicy : EventHubRetryPolicy
+ {
+ /// The seed to use for initializing random number generated for a given thread-specific instance.
+ private static int s_randomSeed = Environment.TickCount;
+
+ /// The random number generator to use for a specific thread.
+ private static readonly ThreadLocal s_random = new ThreadLocal(() => new Random(Interlocked.Increment(ref s_randomSeed)), false);
+
+ ///
+ /// The set of options responsible for configuring the retry
+ /// behavior.
+ ///
+ ///
+ public RetryOptions Options { get; }
+
+ ///
+ /// The factor to apply to the base delay for use as a base jitter value.
+ ///
+ ///
+ /// This factor is used as the basis for random jitter to apply to the calculated retry duration.
+ ///
+ public double JitterFactor { get; } = 0.08;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ ///
+ /// The options which control the retry approach.
+ ///
+ public BasicRetryPolicy(RetryOptions retryOptions)
+ {
+ Guard.ArgumentNotNull(nameof(retryOptions), retryOptions);
+ Options = retryOptions;
+ }
+
+ ///
+ /// Calculates the amount of time to allow the curent attempt for an operation to
+ /// complete before considering it to be timed out.
+ ///
+ ///
+ /// The number of total attempts that have been made, including the initial attempt before any retries.
+ ///
+ /// The amount of time to allow for an operation to complete.
+ ///
+ public override TimeSpan CalculateTryTimeout(int attemptCount) => Options.TryTimeout;
+
+ ///
+ /// Calculates the amount of time to wait before another attempt should be made.
+ ///
+ ///
+ /// The last exception that was observed for the operation to be retried.
+ /// The number of total attempts that have been made, including the initial attempt before any retries.
+ ///
+ /// The amount of time to delay before retrying the associated operation; if null, then the operation is no longer eligible to be retried.
+ ///
+ public override TimeSpan? CalculateRetryDelay(Exception lastException,
+ int attemptCount)
+ {
+ if ((Options.MaximumRetries <= 0)
+ || (Options.Delay == TimeSpan.Zero)
+ || (Options.MaximumDelay == TimeSpan.Zero)
+ || (attemptCount > Options.MaximumRetries)
+ || (!ShouldRetryException(lastException)))
+ {
+ return null;
+ }
+
+ var baseJitterSeconds = (Options.Delay.TotalSeconds * JitterFactor);
+ TimeSpan retryDelay;
+
+ switch (Options.Mode)
+ {
+ case RetryMode.Fixed:
+ retryDelay = CalculateFixedDelay(Options.Delay.TotalSeconds, baseJitterSeconds, s_random.Value);
+ break;
+
+ case RetryMode.Exponential:
+ retryDelay = CalculateExponentiayDelay(attemptCount, Options.Delay.TotalSeconds, baseJitterSeconds, s_random.Value);
+ break;
+
+ default:
+ throw new NotSupportedException(String.Format(CultureInfo.CurrentCulture, Resources.UnknownRetryMode, Options.Mode.ToString()));
+ }
+
+ // Adjust the delay, if needed, to keep within the maximum
+ // duration.
+
+ if (Options.MaximumDelay < retryDelay)
+ {
+ return Options.MaximumDelay;
+ }
+
+ return retryDelay;
+ }
+
+ ///
+ /// Determines if an exception should be retried.
+ ///
+ ///
+ /// The exception to consider.
+ ///
+ /// true to retry the exception; otherwise, false.
+ ///
+ private static bool ShouldRetryException(Exception exception)
+ {
+ if (exception is TaskCanceledException)
+ {
+ exception = exception?.InnerException;
+ }
+ else if (exception is AggregateException aggregateEx)
+ {
+ exception = aggregateEx?.Flatten().InnerException;
+ }
+
+ switch (exception)
+ {
+ case null:
+ return false;
+
+ case EventHubsException ex:
+ return ex.IsTransient;
+
+ case TimeoutException _:
+ case OperationCanceledException _:
+ case SocketException _:
+ return true;
+
+ default:
+ return false;
+ }
+ }
+
+ ///
+ /// Calculates the delay for an exponential backoff.
+ ///
+ ///
+ /// The number of total attempts that have been made, including the initial attempt before any retries.
+ /// The delay to use as a basis for the exponential backoff, in seconds.
+ /// The delay to use as the basis for a random jitter value, in seconds.
+ /// The random number generator to use for the calculation.
+ ///
+ /// The recommended duration to delay before retrying; this value does not take the maximum delay or eligibility for retry into account.
+ ///
+ private static TimeSpan CalculateExponentiayDelay(int attemptCount,
+ double baseDelaySeconds,
+ double baseJitterSeconds,
+ Random random) =>
+ TimeSpan.FromSeconds((Math.Pow(2, attemptCount) * baseDelaySeconds) + (random.NextDouble() * baseJitterSeconds));
+
+ ///
+ /// Calculates the delay for a fixed backoff.
+ ///
+ ///
+ /// The delay to use as a basis for the fixed backoff, in seconds.
+ /// The delay to use as the basis for a random jitter value, in seconds.
+ /// The random number generator to use for the calculation.
+ ///
+ /// The recommended duration to delay before retrying; this value does not take the maximum delay or eligibility for retry into account.
+ ///
+ private static TimeSpan CalculateFixedDelay(double baseDelaySeconds,
+ double baseJitterSeconds,
+ Random random) =>
+ TimeSpan.FromSeconds(baseDelaySeconds + (random.NextDouble() * baseJitterSeconds));
+ }
+}
diff --git a/sdk/eventhub/Azure.Messaging.EventHubs/src/Core/Guard.cs b/sdk/eventhub/Azure.Messaging.EventHubs/src/Core/Guard.cs
index 95333fb617557..ba864db9496d7 100755
--- a/sdk/eventhub/Azure.Messaging.EventHubs/src/Core/Guard.cs
+++ b/sdk/eventhub/Azure.Messaging.EventHubs/src/Core/Guard.cs
@@ -140,7 +140,28 @@ public static void ArgumentInRange(string argumentName,
{
if ((argumentValue < minimumValue) || (argumentValue > maximumValue))
{
- throw new ArgumentOutOfRangeException(argumentName, String.Format(CultureInfo.CurrentCulture, Resources.ValueOutOfRange, argumentName, minimumValue, maximumValue));
+ throw new ArgumentOutOfRangeException(argumentName, String.Format(CultureInfo.CurrentCulture, Resources.ValueOutOfRange, minimumValue, maximumValue));
+ }
+ }
+
+ ///
+ /// Ensures that an argument's value is within a specified range, inclusive.
+ /// if that invariant is not met.
+ ///
+ ///
+ /// The name of the argument being considered.
+ /// The value of the argument to verify.
+ /// The maximum to use for comparison; must be greater than or equal to this value.
+ /// The maximum to use for comparison; must be less than or equal to this value.
+ ///
+ public static void ArgumentInRange(string argumentName,
+ TimeSpan argumentValue,
+ TimeSpan minimumValue,
+ TimeSpan maximumValue)
+ {
+ if ((argumentValue < minimumValue) || (argumentValue > maximumValue))
+ {
+ throw new ArgumentOutOfRangeException(argumentName, String.Format(CultureInfo.CurrentCulture, Resources.ValueOutOfRange, minimumValue, maximumValue));
}
}
}
diff --git a/sdk/eventhub/Azure.Messaging.EventHubs/src/Core/TransportEventHubClient.cs b/sdk/eventhub/Azure.Messaging.EventHubs/src/Core/TransportEventHubClient.cs
index ee7501a30b860..fbc24c793a386 100755
--- a/sdk/eventhub/Azure.Messaging.EventHubs/src/Core/TransportEventHubClient.cs
+++ b/sdk/eventhub/Azure.Messaging.EventHubs/src/Core/TransportEventHubClient.cs
@@ -13,6 +13,14 @@ namespace Azure.Messaging.EventHubs.Core
///
internal abstract class TransportEventHubClient
{
+ ///
+ /// Updates the active retry policy for the client.
+ ///
+ ///
+ /// The retry policy to set as active.
+ ///
+ public abstract void UpdateRetryPolicy(EventHubRetryPolicy newRetryPolicy);
+
///
/// Retrieves information about an Event Hub, including the number of partitions present
/// and their identifiers.
@@ -45,10 +53,12 @@ public abstract Task GetPartitionPropertiesAsync(string par
///
///
/// The set of options to apply when creating the producer.
+ /// The default retry policy to use if no retry options were specified in the .
///
/// An Event Hub producer configured in the requested manner.
///
- public abstract EventHubProducer CreateProducer(EventHubProducerOptions producerOptions);
+ public abstract EventHubProducer CreateProducer(EventHubProducerOptions producerOptions,
+ EventHubRetryPolicy defaultRetryPolicy);
///
/// Creates an Event Hub consumer responsible for reading from a specific Event Hub partition,
@@ -70,13 +80,15 @@ public abstract Task GetPartitionPropertiesAsync(string par
/// The identifier of the Event Hub partition from which events will be received.
/// The position within the partition where the consumer should begin reading events.
/// The set of options to apply when creating the consumer.
+ /// The default retry policy to use if no retry options were specified in the .
///
/// An Event Hub consumer configured in the requested manner.
///
public abstract EventHubConsumer CreateConsumer(string consumerGroup,
string partitionId,
EventPosition eventPosition,
- EventHubConsumerOptions consumerOptions);
+ EventHubConsumerOptions consumerOptions,
+ EventHubRetryPolicy defaultRetryPolicy);
///
/// Closes the connection to the transport client instance.
diff --git a/sdk/eventhub/Azure.Messaging.EventHubs/src/Core/TransportEventHubConsumer.cs b/sdk/eventhub/Azure.Messaging.EventHubs/src/Core/TransportEventHubConsumer.cs
index 08a8b06da273a..eabe3aa0dbd5c 100755
--- a/sdk/eventhub/Azure.Messaging.EventHubs/src/Core/TransportEventHubConsumer.cs
+++ b/sdk/eventhub/Azure.Messaging.EventHubs/src/Core/TransportEventHubConsumer.cs
@@ -1,5 +1,6 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
+
using System;
using System.Collections.Generic;
using System.Threading;
@@ -16,6 +17,14 @@ namespace Azure.Messaging.EventHubs.Core
///
internal abstract class TransportEventHubConsumer
{
+ ///
+ /// Updates the active retry policy for the client.
+ ///
+ ///
+ /// The retry policy to set as active.
+ ///
+ public abstract void UpdateRetryPolicy(EventHubRetryPolicy newRetryPolicy);
+
///
/// Receives a bach of from the the Event Hub partition.
///
diff --git a/sdk/eventhub/Azure.Messaging.EventHubs/src/Core/TransportEventHubProducer.cs b/sdk/eventhub/Azure.Messaging.EventHubs/src/Core/TransportEventHubProducer.cs
index cfe90a157d7f5..8d01b5b13c10d 100755
--- a/sdk/eventhub/Azure.Messaging.EventHubs/src/Core/TransportEventHubProducer.cs
+++ b/sdk/eventhub/Azure.Messaging.EventHubs/src/Core/TransportEventHubProducer.cs
@@ -1,5 +1,6 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
+
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
@@ -15,6 +16,14 @@ namespace Azure.Messaging.EventHubs.Core
///
internal abstract class TransportEventHubProducer
{
+ ///
+ /// Updates the active retry policy for the client.
+ ///
+ ///
+ /// The retry policy to set as active.
+ ///
+ public abstract void UpdateRetryPolicy(EventHubRetryPolicy newRetryPolicy);
+
///
/// Sends a set of events to the associated Event Hub using a batched approach. If the size of events exceed the
/// maximum size of a single batch, an exception will be triggered and the send will fail.
diff --git a/sdk/eventhub/Azure.Messaging.EventHubs/src/Errors/EventHubsTimeoutException.cs b/sdk/eventhub/Azure.Messaging.EventHubs/src/Errors/EventHubsTimeoutException.cs
index e1d3593ffa034..ee8c4535c7564 100755
--- a/sdk/eventhub/Azure.Messaging.EventHubs/src/Errors/EventHubsTimeoutException.cs
+++ b/sdk/eventhub/Azure.Messaging.EventHubs/src/Errors/EventHubsTimeoutException.cs
@@ -19,7 +19,7 @@ public sealed class EventHubsTimeoutException : EventHubsException
/// The name of the Event Hubs resource, such as an Event Hub, consumer group, or partition, to which the exception is associated.
/// The error message that explains the reason for the exception.
///
- internal EventHubsTimeoutException (string resourceName,
+ internal EventHubsTimeoutException(string resourceName,
string message) : this(resourceName, message, null)
{
}
@@ -32,7 +32,7 @@ internal EventHubsTimeoutException (string resourceName,
/// The error message that explains the reason for the exception.
/// The exception that is the cause of the current exception, or a null reference if no inner exception is specified.
///
- internal EventHubsTimeoutException (string resourceName,
+ internal EventHubsTimeoutException(string resourceName,
string message,
Exception innerException) : base(true, resourceName, message, innerException)
{
diff --git a/sdk/eventhub/Azure.Messaging.EventHubs/src/Errors/ServiceBusyException.cs b/sdk/eventhub/Azure.Messaging.EventHubs/src/Errors/ServiceBusyException.cs
index f1d91f32c325b..0812365e36eb9 100755
--- a/sdk/eventhub/Azure.Messaging.EventHubs/src/Errors/ServiceBusyException.cs
+++ b/sdk/eventhub/Azure.Messaging.EventHubs/src/Errors/ServiceBusyException.cs
@@ -19,7 +19,7 @@ public sealed class ServiceBusyException : EventHubsException
/// The name of the Event Hubs resource, such as an Event Hub, consumer group, or partition, to which the exception is associated.
/// The error message that explains the reason for the exception.
///
- internal ServiceBusyException (string resourceName,
+ internal ServiceBusyException(string resourceName,
string message) : this(resourceName, message, null)
{
}
@@ -32,7 +32,7 @@ internal ServiceBusyException (string resourceName,
/// The error message that explains the reason for the exception.
/// The exception that is the cause of the current exception, or a null reference if no inner exception is specified.
///
- internal ServiceBusyException (string resourceName,
+ internal ServiceBusyException(string resourceName,
string message,
Exception innerException) : base(true, resourceName, message, innerException)
{
diff --git a/sdk/eventhub/Azure.Messaging.EventHubs/src/EventHubClient.cs b/sdk/eventhub/Azure.Messaging.EventHubs/src/EventHubClient.cs
index a1b71ff004821..f9c7149d1b28c 100755
--- a/sdk/eventhub/Azure.Messaging.EventHubs/src/EventHubClient.cs
+++ b/sdk/eventhub/Azure.Messaging.EventHubs/src/EventHubClient.cs
@@ -24,6 +24,9 @@ namespace Azure.Messaging.EventHubs
///
public class EventHubClient : IAsyncDisposable
{
+ /// The policy to use for determining retry behavior for when an operation fails.
+ private EventHubRetryPolicy _retryPolicy;
+
///
/// The path of the specific Event Hub that the client is connected to, relative
/// to the Event Hubs namespace that contains it.
@@ -31,6 +34,27 @@ public class EventHubClient : IAsyncDisposable
///
public string EventHubPath { get; }
+ ///
+ /// The policy to use for determining retry behavior for when an operation fails.
+ ///
+ ///
+ public EventHubRetryPolicy RetryPolicy
+ {
+ get => _retryPolicy;
+
+ set
+ {
+ Guard.ArgumentNotNull(nameof(RetryPolicy), value);
+ _retryPolicy = value;
+
+ // Applying a custom retry policy invalidates the retry options specified.
+ // Clear them to ensure the custom policy is propagated as the default.
+
+ ClientOptions.ClearRetryOptions();
+ InnerClient.UpdateRetryPolicy(value);
+ }
+ }
+
///
/// An abstracted Event Hub Client specific to the active protocol and transport intended to perform delegated operations.
///
@@ -141,9 +165,10 @@ public EventHubClient(string connectionString,
connectionStringProperties.SharedAccessKey
);
+ _retryPolicy = new BasicRetryPolicy(clientOptions.RetryOptions);
ClientOptions = clientOptions;
EventHubPath = eventHubPath;
- InnerClient = BuildTransportClient(eventHubsHostName, eventHubPath, new SharedAccessSignatureCredential(sharedAccessSignature), clientOptions);
+ InnerClient = BuildTransportClient(eventHubsHostName, eventHubPath, new SharedAccessSignatureCredential(sharedAccessSignature), clientOptions, _retryPolicy);
}
///
@@ -181,9 +206,10 @@ public EventHubClient(string host,
break;
}
+ _retryPolicy = new BasicRetryPolicy(clientOptions.RetryOptions);
EventHubPath = eventHubPath;
ClientOptions = clientOptions;
- InnerClient = BuildTransportClient(host, eventHubPath, credential, clientOptions);
+ InnerClient = BuildTransportClient(host, eventHubPath, credential, clientOptions, _retryPolicy);
}
///
@@ -276,12 +302,10 @@ public virtual Task GetPartitionPropertiesAsync(string part
///
public virtual EventHubProducer CreateProducer(EventHubProducerOptions producerOptions = default)
{
- var options = producerOptions?.Clone() ?? new EventHubProducerOptions { Retry = null, Timeout = null };
-
- options.Retry = options.Retry ?? ClientOptions.Retry.Clone();
- options.Timeout = options.TimeoutOrDefault ?? ClientOptions.DefaultTimeout;
+ var options = producerOptions?.Clone() ?? new EventHubProducerOptions { RetryOptions = null };
+ options.RetryOptions = options.RetryOptions ?? ClientOptions.RetryOptions?.Clone();
- return InnerClient.CreateProducer(options);
+ return InnerClient.CreateProducer(options, _retryPolicy);
}
///
@@ -315,12 +339,10 @@ public virtual EventHubConsumer CreateConsumer(string consumerGroup,
Guard.ArgumentNotNullOrEmpty(nameof(partitionId), partitionId);
Guard.ArgumentNotNull(nameof(eventPosition), eventPosition);
- var options = consumerOptions?.Clone() ?? new EventHubConsumerOptions { Retry = null, DefaultMaximumReceiveWaitTime = null };
-
- options.Retry = options.Retry ?? ClientOptions.Retry.Clone();
- options.DefaultMaximumReceiveWaitTime = options.MaximumReceiveWaitTimeOrDefault ?? ClientOptions.DefaultTimeout;
+ var options = consumerOptions?.Clone() ?? new EventHubConsumerOptions { RetryOptions = null };
+ options.RetryOptions = options.RetryOptions ?? ClientOptions.RetryOptions?.Clone();
- return InnerClient.CreateConsumer(consumerGroup, partitionId, eventPosition, options);
+ return InnerClient.CreateConsumer(consumerGroup, partitionId, eventPosition, options, _retryPolicy);
}
///
@@ -388,6 +410,7 @@ public virtual EventHubConsumer CreateConsumer(string consumerGroup,
/// The path to a specific Event Hub.
/// The Azure managed identity credential to use for authorization.
/// The set of options to use for the client.
+ /// The default retry policy to use if no retry options were specified in the .
///
/// A client generalization spcecific to the specified protocol/transport to which operations may be delegated.
///
@@ -402,13 +425,14 @@ public virtual EventHubConsumer CreateConsumer(string consumerGroup,
internal virtual TransportEventHubClient BuildTransportClient(string host,
string eventHubPath,
TokenCredential credential,
- EventHubClientOptions options)
+ EventHubClientOptions options,
+ EventHubRetryPolicy defaultRetryPolicy)
{
switch (options.TransportType)
{
case TransportType.AmqpTcp:
case TransportType.AmqpWebSockets:
- return new TrackOneEventHubClient(host, eventHubPath, credential, options);
+ return new TrackOneEventHubClient(host, eventHubPath, credential, options, defaultRetryPolicy);
default:
throw new ArgumentException(String.Format(CultureInfo.CurrentCulture, Resources.InvalidTransportType, options.TransportType.ToString()), nameof(options.TransportType));
diff --git a/sdk/eventhub/Azure.Messaging.EventHubs/src/EventHubClientOptions.cs b/sdk/eventhub/Azure.Messaging.EventHubs/src/EventHubClientOptions.cs
index 9a74c434bba74..d0a068666a8b9 100755
--- a/sdk/eventhub/Azure.Messaging.EventHubs/src/EventHubClientOptions.cs
+++ b/sdk/eventhub/Azure.Messaging.EventHubs/src/EventHubClientOptions.cs
@@ -14,14 +14,8 @@ namespace Azure.Messaging.EventHubs
///
public class EventHubClientOptions
{
- /// The value to use as the default for the property.
- private static readonly TimeSpan DefaultTimeoutValue = TimeSpan.FromMinutes(1);
-
- /// The retry policy to apply to operations.
- private Retry _retry = Retry.Default;
-
- /// the timeout that will be used by default for operations.
- private TimeSpan _defaultTimeout = DefaultTimeoutValue;
+ /// The set of options to apply for retrying failed operations.
+ private RetryOptions _retryOptions = new RetryOptions();
///
/// The type of protocol and transport that will be used for communicating with the Event Hubs
@@ -43,44 +37,21 @@ public class EventHubClientOptions
public IWebProxy Proxy { get; set; } = null;
///
- /// The policy to use for determining whether a failed operation should be retried and,
+ /// The set of options to use for determining whether a failed operation should be retried and,
/// if so, the amount of time to wait between retry attempts.
///
///
- public Retry Retry
+ public RetryOptions RetryOptions
{
- get => _retry;
+ get => _retryOptions;
set
{
- ValidateRetry(value);
- _retry = value;
+ ValidateRetryOptions(value);
+ _retryOptions = value;
}
}
- ///
- /// Gets or sets the timeout that will be used by default for operations associated with
- /// the requested Event Hub.
- ///
- ///
- public TimeSpan DefaultTimeout
- {
- get => _defaultTimeout;
-
- set
- {
- ValidateDefaultTimeout(value);
- _defaultTimeout = value;
- }
- }
-
- ///
- /// Normalizes the specified timeout value, returning the timeout period or the
- /// a null value if no timeout was specified.
- ///
- ///
- internal TimeSpan? TimeoutOrDefault => (_defaultTimeout == TimeSpan.Zero) ? DefaultTimeoutValue : _defaultTimeout;
-
///
/// Determines whether the specified , is equal to this instance.
///
@@ -119,38 +90,29 @@ public TimeSpan DefaultTimeout
internal EventHubClientOptions Clone() =>
new EventHubClientOptions
{
- Retry = this.Retry.Clone(),
TransportType = this.TransportType,
- DefaultTimeout = this.DefaultTimeout,
- Proxy = this.Proxy
+ Proxy = this.Proxy,
+ _retryOptions = this.RetryOptions.Clone()
};
///
- /// Validates the time period specified as the default operation timeout, throwing an if
- /// it is not valid.
+ /// Clears the retry options to allow for bypassing them in the
+ /// case where a custom retry policy is used.
///
///
- /// The time period to validate.
- ///
- private void ValidateDefaultTimeout(TimeSpan timeout)
- {
- if (timeout < TimeSpan.Zero)
- {
- throw new ArgumentException(Resources.TimeoutMustBePositive, nameof(DefaultTimeout));
- }
- }
+ internal void ClearRetryOptions() => _retryOptions = null;
///
- /// Validates the retry policy specified, throwing an if it is not valid.
+ /// Validates the retry options are specified, throwing an if it is not valid.
///
///
- /// The time period to validae.
+ /// The set of retry options to validae.
///
- private void ValidateRetry(Retry retry)
+ private void ValidateRetryOptions(RetryOptions retryOptions)
{
- if (retry == null)
+ if (retryOptions == null)
{
- throw new ArgumentException(Resources.RetryMustBeSet, nameof(Retry));
+ throw new ArgumentException(Resources.RetryOptionsMustBeSet, nameof(RetryOptions));
}
}
}
diff --git a/sdk/eventhub/Azure.Messaging.EventHubs/src/EventHubConsumer.cs b/sdk/eventhub/Azure.Messaging.EventHubs/src/EventHubConsumer.cs
index 8e8d555eeb2cf..c3ac2b46991c5 100755
--- a/sdk/eventhub/Azure.Messaging.EventHubs/src/EventHubConsumer.cs
+++ b/sdk/eventhub/Azure.Messaging.EventHubs/src/EventHubConsumer.cs
@@ -28,6 +28,9 @@ public class EventHubConsumer : IAsyncDisposable
/// The name of the default consumer group in the Event Hubs service.
public const string DefaultConsumerGroupName = "$Default";
+ /// The policy to use for determining retry behavior for when an operation fails.
+ private EventHubRetryPolicy _retryPolicy;
+
///
/// The identifier of the Event Hub partition that this consumer is associated with. Events will be read
/// only from this partition.
@@ -68,6 +71,27 @@ public class EventHubConsumer : IAsyncDisposable
///
public string Identifier => Options?.Identifier;
+ ///
+ /// The policy to use for determining retry behavior for when an operation fails.
+ ///
+ ///
+ public EventHubRetryPolicy RetryPolicy
+ {
+ get => _retryPolicy;
+
+ set
+ {
+ Guard.ArgumentNotNull(nameof(RetryPolicy), value);
+ _retryPolicy = value;
+
+ // Applying a custom retry policy invalidates the retry options specified.
+ // Clear them to ensure the custom policy is propagated as the default.
+
+ Options.RetryOptions = null;
+ InnerConsumer.UpdateRetryPolicy(value);
+ }
+ }
+
///
/// The set of consumer options used for creation of this consumer.
///
@@ -90,6 +114,7 @@ public class EventHubConsumer : IAsyncDisposable
/// The name of the consumer group this consumer is associated with. Events are read in the context of this group.
/// The position within the partition where the consumer should begin reading events.
/// The set of options to use for this consumer.
+ /// The policy to apply when making retry decisions for failed operations.
///
///
/// If the starting event position is not specified in the , the consumer will
@@ -106,7 +131,8 @@ internal EventHubConsumer(TransportEventHubConsumer transportConsumer,
string consumerGroup,
string partitionId,
EventPosition eventPosition,
- EventHubConsumerOptions consumerOptions)
+ EventHubConsumerOptions consumerOptions,
+ EventHubRetryPolicy retryPolicy)
{
Guard.ArgumentNotNull(nameof(transportConsumer), transportConsumer);
Guard.ArgumentNotNullOrEmpty(nameof(eventHubPath), eventHubPath);
@@ -114,6 +140,7 @@ internal EventHubConsumer(TransportEventHubConsumer transportConsumer,
Guard.ArgumentNotNullOrEmpty(nameof(partitionId), partitionId);
Guard.ArgumentNotNull(nameof(eventPosition), eventPosition);
Guard.ArgumentNotNull(nameof(consumerOptions), consumerOptions);
+ Guard.ArgumentNotNull(nameof(retryPolicy), retryPolicy);
PartitionId = partitionId;
StartingPosition = eventPosition;
@@ -121,6 +148,8 @@ internal EventHubConsumer(TransportEventHubConsumer transportConsumer,
ConsumerGroup = consumerGroup;
Options = consumerOptions;
InnerConsumer = transportConsumer;
+
+ _retryPolicy = retryPolicy;
}
///
diff --git a/sdk/eventhub/Azure.Messaging.EventHubs/src/EventHubConsumerOptions.cs b/sdk/eventhub/Azure.Messaging.EventHubs/src/EventHubConsumerOptions.cs
index c9d6abc6912bd..d43268fcadcc4 100755
--- a/sdk/eventhub/Azure.Messaging.EventHubs/src/EventHubConsumerOptions.cs
+++ b/sdk/eventhub/Azure.Messaging.EventHubs/src/EventHubConsumerOptions.cs
@@ -45,13 +45,12 @@ public class EventHubConsumerOptions
public long? OwnerLevel { get; set; }
///
- /// The used to govern retry attempts when an issue
- /// is encountered while receiving.
+ /// The set of options to use for determining whether a failed operation should be retried and,
+ /// if so, the amount of time to wait between retry attempts. If not specified, the retry policy from
+ /// the associcated will be used.
///
///
- /// If not specified, the retry policy configured on the associated will be used.
- ///
- public Retry Retry { get; set; }
+ public RetryOptions RetryOptions { get; set; }
///
/// The default amount of time to wait for the requested amount of messages when receiving; if this
@@ -151,8 +150,7 @@ internal EventHubConsumerOptions Clone() =>
new EventHubConsumerOptions
{
OwnerLevel = this.OwnerLevel,
- Retry = this.Retry?.Clone(),
-
+ RetryOptions = this.RetryOptions?.Clone(),
_identifier = this._identifier,
_prefetchCount = this._prefetchCount,
_maximumReceiveWaitTime = this._maximumReceiveWaitTime
@@ -187,5 +185,19 @@ private void ValidateMaximumReceiveWaitTime(TimeSpan? maximumWaitTime)
throw new ArgumentException(Resources.TimeoutMustBePositive, nameof(DefaultMaximumReceiveWaitTime));
}
}
+
+ ///
+ /// Validates the retry options are specified, throwing an if it is not valid.
+ ///
+ ///
+ /// The set of retry options to validae.
+ ///
+ private void ValidateRetryOptions(RetryOptions retryOptions)
+ {
+ if (retryOptions == null)
+ {
+ throw new ArgumentException(Resources.RetryOptionsMustBeSet, nameof(RetryOptions));
+ }
+ }
}
}
diff --git a/sdk/eventhub/Azure.Messaging.EventHubs/src/EventHubProducer.cs b/sdk/eventhub/Azure.Messaging.EventHubs/src/EventHubProducer.cs
index 3b9503619f4f6..5630ab9286c72 100755
--- a/sdk/eventhub/Azure.Messaging.EventHubs/src/EventHubProducer.cs
+++ b/sdk/eventhub/Azure.Messaging.EventHubs/src/EventHubProducer.cs
@@ -39,6 +39,9 @@ public class EventHubProducer : IAsyncDisposable
/// The set of default batching options to use when no specific options are requested.
private static readonly SendOptions DefaultSendOptions = new SendOptions();
+ /// The policy to use for determining retry behavior for when an operation fails.
+ private EventHubRetryPolicy _retryPolicy;
+
///
/// The identifier of the Event Hub partition that the is bound to, indicating
/// that it will send events to only that partition.
@@ -58,6 +61,27 @@ public class EventHubProducer : IAsyncDisposable
///
public string EventHubPath { get; }
+ ///
+ /// The policy to use for determining retry behavior for when an operation fails.
+ ///
+ ///
+ public EventHubRetryPolicy RetryPolicy
+ {
+ get => _retryPolicy;
+
+ set
+ {
+ Guard.ArgumentNotNull(nameof(RetryPolicy), value);
+ _retryPolicy = value;
+
+ // Applying a custom retry policy invalidates the retry options specified.
+ // Clear them to ensure the custom policy is propagated as the default.
+
+ Options.RetryOptions = null;
+ InnerProducer.UpdateRetryPolicy(value);
+ }
+ }
+
///
/// The set of options used for creation of this producer.
///
@@ -77,6 +101,7 @@ public class EventHubProducer : IAsyncDisposable
/// An abstracted Event Hub producer specific to the active protocol and transport intended to perform delegated operations.
/// The path of the Event Hub to which events will be sent.
/// The set of options to use for this consumer.
+ /// The policy to apply when making retry decisions for failed operations.
///
///
/// Because this is a non-public constructor, it is assumed that the passed are
@@ -86,16 +111,20 @@ public class EventHubProducer : IAsyncDisposable
///
internal EventHubProducer(TransportEventHubProducer transportProducer,
string eventHubPath,
- EventHubProducerOptions producerOptions)
+ EventHubProducerOptions producerOptions,
+ EventHubRetryPolicy retryPolicy)
{
Guard.ArgumentNotNull(nameof(transportProducer), transportProducer);
Guard.ArgumentNotNullOrEmpty(nameof(eventHubPath), eventHubPath);
Guard.ArgumentNotNull(nameof(producerOptions), producerOptions);
+ Guard.ArgumentNotNull(nameof(retryPolicy), retryPolicy);
PartitionId = producerOptions.PartitionId;
EventHubPath = eventHubPath;
Options = producerOptions;
InnerProducer = transportProducer;
+
+ _retryPolicy = retryPolicy;
}
///
diff --git a/sdk/eventhub/Azure.Messaging.EventHubs/src/EventHubProducerOptions.cs b/sdk/eventhub/Azure.Messaging.EventHubs/src/EventHubProducerOptions.cs
index e8980623938e6..62cbce337e61b 100755
--- a/sdk/eventhub/Azure.Messaging.EventHubs/src/EventHubProducerOptions.cs
+++ b/sdk/eventhub/Azure.Messaging.EventHubs/src/EventHubProducerOptions.cs
@@ -14,9 +14,6 @@ namespace Azure.Messaging.EventHubs
///
public class EventHubProducerOptions
{
- /// The timeout that will be used by default for sending events.
- private TimeSpan? _timeout = TimeSpan.FromMinutes(1);
-
/// The identifier of the partition that the producer will be bound to.
private string _partitionId = null;
@@ -51,39 +48,12 @@ public string PartitionId
}
///
- /// The used to govern retry attempts when an issue is
- /// encountered while sending.
- ///
- ///
- /// If not specified, the retry policy configured on the associated will be used.
- ///
- public Retry Retry { get; set; }
-
- ///
- /// The default timeout to apply when sending events. If the timeout is reached, before the Event Hub
- /// acknowledges receipt of the event data being sent, the attempt will be classified as failed and considered
- /// to be retried.
- ///
- ///
- /// If not specified, the operation timeout requested for the associated will be used.
- ///
- public TimeSpan? Timeout
- {
- get => _timeout;
-
- set
- {
- ValidateTimeout(value);
- _timeout = value;
- }
- }
-
- ///
- /// Normalizes the specified timeout value, returning the timeout period or the
- /// a null value if no timeout was specified.
+ /// The set of options to use for determining whether a failed operation should be retried and,
+ /// if so, the amount of time to wait between retry attempts. If not specified, the retry policy from
+ /// the associcated will be used.
///
///
- internal TimeSpan? TimeoutOrDefault => (_timeout == TimeSpan.Zero) ? null : _timeout;
+ public RetryOptions RetryOptions { get; set; }
///
/// Determines whether the specified , is equal to this instance.
@@ -123,24 +93,8 @@ public TimeSpan? Timeout
internal EventHubProducerOptions Clone() =>
new EventHubProducerOptions
{
- _partitionId = this.PartitionId,
- Retry = this.Retry?.Clone(),
- Timeout = this.Timeout
+ RetryOptions = this.RetryOptions?.Clone(),
+ _partitionId = this.PartitionId
};
-
- ///
- /// Validates the time period specified as the timeout to use when sending vents, throwing an if
- /// it is not valid.
- ///
- ///
- /// The time period to validate.
- ///
- protected virtual void ValidateTimeout(TimeSpan? timeout)
- {
- if (timeout < TimeSpan.Zero)
- {
- throw new ArgumentException(Resources.TimeoutMustBePositive, nameof(Timeout));
- }
- }
}
}
diff --git a/sdk/eventhub/Azure.Messaging.EventHubs/src/EventHubRetryPolicy.cs b/sdk/eventhub/Azure.Messaging.EventHubs/src/EventHubRetryPolicy.cs
new file mode 100755
index 0000000000000..55277e72b4aa7
--- /dev/null
+++ b/sdk/eventhub/Azure.Messaging.EventHubs/src/EventHubRetryPolicy.cs
@@ -0,0 +1,75 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+
+using System;
+using System.ComponentModel;
+
+namespace Azure.Messaging.EventHubs
+{
+ ///
+ /// An abstract representation of a policy to govern retrying of messaging operations.
+ ///
+ ///
+ ///
+ /// It is recommended that consumers not implement custom retry policies but instead
+ /// configure the default policy by specifying the desired set of retry options when
+ /// creating an .
+ ///
+ ///
+ ///
+ ///
+ public abstract class EventHubRetryPolicy
+ {
+ ///
+ /// Calculates the amount of time to allow the curent attempt for an operation to
+ /// complete before considering it to be timed out.
+ ///
+ ///
+ /// The number of total attempts that have been made, including the initial attempt before any retries.
+ ///
+ /// The amount of time to allow for an operation to complete.
+ ///
+ public abstract TimeSpan CalculateTryTimeout(int attemptCount);
+
+ ///
+ /// Calculates the amount of time to wait before another attempt should be made.
+ ///
+ ///
+ /// The last exception that was observed for the operation to be retried.
+ /// The number of total attempts that have been made, including the initial attempt before any retries.
+ ///
+ /// The amount of time to delay before retrying the associated operation; if null, then the operation is no longer eligible to be retried.
+ ///
+ public abstract TimeSpan? CalculateRetryDelay(Exception lastException,
+ int attemptCount);
+
+ ///
+ /// Determines whether the specified , is equal to this instance.
+ ///
+ ///
+ /// The to compare with this instance.
+ ///
+ /// true if the specified is equal to this instance; otherwise, false.
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public override bool Equals(object obj) => base.Equals(obj);
+
+ ///
+ /// Returns a hash code for this instance.
+ ///
+ ///
+ /// A hash code for this instance, suitable for use in hashing algorithms and data structures like a hash table.
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public override int GetHashCode() => base.GetHashCode();
+
+ ///
+ /// Converts the instance to string representation.
+ ///
+ ///
+ /// A that represents this instance.
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public override string ToString() => base.ToString();
+ }
+}
diff --git a/sdk/eventhub/Azure.Messaging.EventHubs/src/ExponentialRetry.cs b/sdk/eventhub/Azure.Messaging.EventHubs/src/ExponentialRetry.cs
deleted file mode 100755
index 82695132ac81a..0000000000000
--- a/sdk/eventhub/Azure.Messaging.EventHubs/src/ExponentialRetry.cs
+++ /dev/null
@@ -1,190 +0,0 @@
-// Copyright (c) Microsoft Corporation. All rights reserved.
-// Licensed under the MIT License.
-
-using System;
-using System.ComponentModel;
-using Azure.Messaging.EventHubs.Core;
-
-namespace Azure.Messaging.EventHubs
-{
- ///
- /// A policy to govern retrying of messaging operations in which the delay between retries will grow in an
- /// exponential manner, allowing more time to recover as the number of retries increases.
- ///
- ///
- ///
- ///
- public sealed class ExponentialRetry : Retry
- {
- /// The minimum time period permissible for backing off between retries.
- private readonly TimeSpan _minimumBackoff;
-
- /// The maximum time period permissible for backing off between retries.
- private readonly TimeSpan _maximumBackoff;
-
- /// The maximum time period permissible for backing off between retries.
- private readonly int _maximumRetryCount;
-
- /// The exponential base for retry interval calculations.
- private readonly double _retryFactor;
-
- ///
- /// Initializes a new instance of the class.
- ///
- ///
- /// The minimum time period permissible for backing off between retries.
- /// The maximum time period permissible for backing off between retries.
- /// The maximum number of retries allowed.
- ///
- public ExponentialRetry(TimeSpan minimumBackoff,
- TimeSpan maximumBackoff,
- int maximumRetryCount)
- {
- Guard.ArgumentNotNegative(nameof(minimumBackoff), minimumBackoff);
- Guard.ArgumentNotNegative(nameof(maximumBackoff), maximumBackoff);
-
- _minimumBackoff = minimumBackoff;
- _maximumBackoff = maximumBackoff;
- _maximumRetryCount = maximumRetryCount;
- _retryFactor = ComputeRetryFactor(minimumBackoff, maximumBackoff, maximumRetryCount);
- }
-
- ///
- /// Determines whether the specified , is equal to this instance.
- ///
- ///
- /// The to compare with this instance.
- ///
- /// true if the specified is equal to this instance; otherwise, false.
- ///
- [EditorBrowsable(EditorBrowsableState.Never)]
- public override bool Equals(object obj) => base.Equals(obj);
-
- ///
- /// Returns a hash code for this instance.
- ///
- ///
- /// A hash code for this instance, suitable for use in hashing algorithms and data structures like a hash table.
- ///
- [EditorBrowsable(EditorBrowsableState.Never)]
- public override int GetHashCode() => base.GetHashCode();
-
- ///
- /// Converts the instance to string representation.
- ///
- ///
- /// A that represents this instance.
- ///
- [EditorBrowsable(EditorBrowsableState.Never)]
- public override string ToString() => base.ToString();
-
- ///
- /// Creates a new copy of the current , cloning its attributes into a new instance.
- ///
- ///
- /// A new copy of .
- ///
- internal override Retry Clone() =>
- new ExponentialRetry(_minimumBackoff, _maximumBackoff, _maximumRetryCount);
-
- ///
- /// Allows internal access to the retry properties for compatibility shims.
- ///
- ///
- /// The set of properties for the retry policy.
- ///
- ///
- /// This method is intended to allow for compatibility shims to create the equivilent retry
- /// policy within the track one code; it will be removed after the first preview and should
- /// not be depended upon outside of that context.
- ///
- ///
- internal (TimeSpan minimumBackOff, TimeSpan maximumBackoff, int maximumRetryCount) GetProperties() => (_minimumBackoff, _maximumBackoff, _maximumRetryCount); //TODO: Remove after preview
-
- ///
- /// Determines whether the specified , is equal to this instance.
- ///
- ///
- /// The first to consider.
- /// The second to consider.
- ///
- /// true if the specified is equal to this instance; otherwise, false.
- ///
- internal static bool HaveSameConfiguration(ExponentialRetry first, ExponentialRetry second)
- {
- if (ReferenceEquals(first, second))
- {
- return true;
- }
-
- if ((first == null) || (second == null))
- {
- return false;
- }
-
- return (first._minimumBackoff == second._minimumBackoff)
- && (first._maximumBackoff == second._maximumBackoff)
- && (first._maximumRetryCount == second._maximumRetryCount);
- }
-
- ///
- /// Allows a concrete retry policy implementation to offer a base retry interval to be used in
- /// the calculations performed by .
- ///
- ///
- /// The last exception that was observed for the operation to be retried.
- /// The amount of time remaining for the cumulative timeout across retry attempts.
- /// The number of seconds to base the suggested retry interval on; this should be used as the minimum interval returned under normal circumstances.
- /// The number of retries that have already been attempted.
- ///
- /// The amount of time to delay before retrying the associated operation; if null, then the operation is no longer eligible to be retried.
- ///
- ///
- /// The interval produced by this method is considered non-authoritative and is subject to adjustment by the
- /// implementation.
- ///
- ///
- protected override TimeSpan? CalculateNextRetryInterval(Exception lastException,
- TimeSpan remainingTime,
- int baseWaitSeconds,
- int retryCount)
- {
- if ((!IsRetriableException(lastException)) || (retryCount >= _maximumRetryCount))
- {
- return null;
- }
-
- double nextRetryInterval = Math.Pow(_retryFactor, (double)retryCount);
- long nextRetryIntervalSeconds = (long)nextRetryInterval;
- long nextRetryIntervalMilliseconds = (long)((nextRetryInterval - (double)nextRetryIntervalSeconds) * 1000);
- TimeSpan retryAfter = _minimumBackoff.Add(TimeSpan.FromMilliseconds(nextRetryIntervalSeconds * 1000 + nextRetryIntervalMilliseconds));
-
- return retryAfter.Add(TimeSpan.FromSeconds(baseWaitSeconds));
- }
-
- ///
- /// Computes the base factor to be used in calculating the exponential component of
- /// the retry interval.
- ///
- ///
- /// The minimum time period permissible for backing off between retries.
- /// The maximum time period permissible for backing off between retries.
- /// The maximum number of retries allowed.
- ///
- /// The exponential base for retry interval calculations.
- ///
- private double ComputeRetryFactor(TimeSpan minimumBackoff,
- TimeSpan maximumBackoff,
- int maximumRetryCount)
- {
- double deltaBackoff = maximumBackoff.Subtract(minimumBackoff).TotalSeconds;
-
- if ((deltaBackoff <= 0) || (maximumRetryCount <= 0))
- {
- return 0;
- }
-
- return (Math.Log(deltaBackoff) / Math.Log(maximumRetryCount));
- }
- }
-}
diff --git a/sdk/eventhub/Azure.Messaging.EventHubs/src/Resources.Designer.cs b/sdk/eventhub/Azure.Messaging.EventHubs/src/Resources.Designer.cs
index 3b7201f86b82f..8b6403be97017 100755
--- a/sdk/eventhub/Azure.Messaging.EventHubs/src/Resources.Designer.cs
+++ b/sdk/eventhub/Azure.Messaging.EventHubs/src/Resources.Designer.cs
@@ -69,9 +69,9 @@ internal Resources()
///
/// Looks up a localized string similar to The message (id:{0}, size:{1} bytes) is larger than is currently allowed ({2} bytes)..
///
- internal static string MessageSizeExceeded
+ internal static string MessageSizeExceeded
{
- get
+ get
{
return ResourceManager.GetString("MessageSizeExceeded", resourceCulture);
}
@@ -243,7 +243,7 @@ internal static string ConsumerIdentifierOverMaxValue
}
///
- /// Looks up a localized string similar to The reqested resource, '{0}', does not match the resource of the shared access signature, '{1}'. A token cannot be issued..
+ /// Looks up a localized string similar to The requested resource, '{0}', does not match the resource of the shared access signature, '{1}'. A token cannot be issued..
///
internal static string ResourceMustMatchSharedAccessSignature
{
@@ -254,13 +254,13 @@ internal static string ResourceMustMatchSharedAccessSignature
}
///
- /// Looks up a localized string similar to A retry must be set for the options; if no retry is desired, please set the value to Retry.NoRetry.
+ /// Looks up a localized string similar to Retry options must be specified; if no retry is desired, please set the maximum number of retries to 0. To provide a custom retry policy, please assign it on the client directly..
///
- internal static string RetryMustBeSet
+ internal static string RetryOptionsMustBeSet
{
get
{
- return ResourceManager.GetString("RetryMustBeSet", resourceCulture);
+ return ResourceManager.GetString("RetryOptionsMustBeSet", resourceCulture);
}
}
@@ -351,5 +351,16 @@ internal static string CannotParseIntegerType
return ResourceManager.GetString("CannotParseIntegerType", resourceCulture);
}
}
+
+ ///
+ /// Looks up a localized string similar to The requested retry mode, '{0}' is not known; a retry delay canot be determined..
+ ///
+ internal static string UnknownRetryMode
+ {
+ get
+ {
+ return ResourceManager.GetString("UnknownRetryMode", resourceCulture);
+ }
+ }
}
}
diff --git a/sdk/eventhub/Azure.Messaging.EventHubs/src/Resources.resx b/sdk/eventhub/Azure.Messaging.EventHubs/src/Resources.resx
index adc02451d183f..1eac645a63885 100755
--- a/sdk/eventhub/Azure.Messaging.EventHubs/src/Resources.resx
+++ b/sdk/eventhub/Azure.Messaging.EventHubs/src/Resources.resx
@@ -145,7 +145,7 @@
The 'identifier' parameter exceeds the maximum allowed size of {0} characters.
- A timeout value must be positive. To request using the default timeout, please use TimeSpan.Zero or null.
+ A timeout value must be positive. To request using the default timeout, please use TimeSpan.Zero or null.Argument {0} must be a non-negative timespan value. The provided value was {1}.
@@ -156,14 +156,14 @@
A proxy may only be used for a websockets connection.
-
- A retry must be set for the options; if no retry is desired, please set the value to Retry.NoRetry
+
+ Retry options must be specified; if no retry is desired, please set the maximum number of retries to 0. To provide a custom retry policy, please assign it on the client directly.The connection string could not be parsed; either it was malformed or contains no well-known tokens.
- The connection string used for an Event Hub client must specify the Event Hubs namespace host, and a Shared Access Signature (both the name and value) to be valid. The path to an Event Hub must be included in the connection string or specified separately.
+ The connection string used for an Event Hub client must specify the Event Hubs namespace host, and a Shared Access Signature (both the name and value) to be valid. The path to an Event Hub must be included in the connection string or specified separately.The path to an Event Hub may be specified as part of the connection string or as a separate value, but not both.
@@ -178,21 +178,24 @@
In order to update the signature, a shared access key must have been provided when the shared access signature was created.
- The reqested resource, '{0}', does not match the resource of the shared access signature, '{1}'. A token cannot be issued.
+ The requested resource, '{0}', does not match the resource of the shared access signature, '{1}'. A token cannot be issued.The requested transport type, '{0}' is not supported.
- A producer created for a specific partition cannot send events using a partition key. This producer is associated with partition '{0}'.
+ A producer created for a specific partition cannot send events using a partition key. This producer is associated with partition '{0}'.
- The credential is not a known and supported credential type. Please use a JWT credential or shared key credential.
+ The credential is not a known and supported credential type. Please use a JWT credential or shared key credential.
- A shared key credential is unable to generate a token directly. Please use this credential when creating an Event Hub Client, for proper generation of shared key tokens.
+ A shared key credential is unable to generate a token directly. Please use this credential when creating an Event Hub Client, for proper generation of shared key tokens.The {0} value is expected to be a {1} bit signed integer. Actual value: '{2}'.
+
+ The requested retry mode, '{0}', is not known; a retry delay canot be determined.
+
\ No newline at end of file
diff --git a/sdk/eventhub/Azure.Messaging.EventHubs/src/Retry.cs b/sdk/eventhub/Azure.Messaging.EventHubs/src/Retry.cs
deleted file mode 100755
index dc728c60179f2..0000000000000
--- a/sdk/eventhub/Azure.Messaging.EventHubs/src/Retry.cs
+++ /dev/null
@@ -1,127 +0,0 @@
-// Copyright (c) Microsoft Corporation. All rights reserved.
-// Licensed under the MIT License.
-
-using System;
-using System.ComponentModel;
-
-namespace Azure.Messaging.EventHubs
-{
- ///
- /// An abstract representation of a policy to govern retrying of messaging operations.
- ///
- ///
- ///
- /// It is recommended that consumers not implement custom retry policies but instead
- /// use one of the provided implementations.
- ///
- ///
- ///
- ///
- ///
- ///
- public abstract class Retry
- {
- /// The maximum number of retries allowed by default when no value is specified.
- private const int DefaultMaximumRetryCount = 10;
-
- /// The minimum time period permissible for backing off between retries by default when no value is specified.
- private static readonly TimeSpan DefaultRetryMinimumBackoff = TimeSpan.Zero;
-
- /// The maximum time period permissible for backing off between retries by default when no value is specified.
- private static readonly TimeSpan DefaultRetryMaximumBackoff = TimeSpan.FromSeconds(30);
-
- ///
- /// The default retry policy, recommended for most general purpose scenarios.
- ///
- ///
- public static Retry Default => new ExponentialRetry(DefaultRetryMinimumBackoff, DefaultRetryMaximumBackoff, DefaultMaximumRetryCount);
-
- ///
- /// A policy that disallows retries alltogether.
- ///
- ///
- public static Retry NoRetry => new ExponentialRetry(TimeSpan.Zero, TimeSpan.Zero, 0);
-
- ///
- /// Determines whether or not a given exception is eligible for a retry attempt.
- ///
- ///
- /// The exception to consider.
- ///
- /// true if the operation that produced the exception may be retried; otherwise, false.
- ///
- internal virtual bool IsRetriableException(Exception exception) => throw new NotImplementedException();
-
- ///
- /// Calculates the amount of time to delay before the next retry attempt.
- ///
- ///
- /// The last exception that was observed for the operation to be retried.
- /// The amount of time remaining for the cumulative timeout across retry attempts.
- /// The number of retries that have already been attempted.
- ///
- /// The amount of time to delay before retrying the associated operation; if null, then the operation is no longer eligible to be retried.
- ///
- internal TimeSpan? GetNextRetryInterval(Exception lastException,
- TimeSpan remainingTime,
- int retryCount) => throw new NotImplementedException();
- ///
- /// Determines whether the specified , is equal to this instance.
- ///
- ///
- /// The to compare with this instance.
- ///
- /// true if the specified is equal to this instance; otherwise, false.
- ///
- [EditorBrowsable(EditorBrowsableState.Never)]
- public override bool Equals(object obj) => base.Equals(obj);
-
- ///
- /// Returns a hash code for this instance.
- ///
- ///
- /// A hash code for this instance, suitable for use in hashing algorithms and data structures like a hash table.
- ///
- [EditorBrowsable(EditorBrowsableState.Never)]
- public override int GetHashCode() => base.GetHashCode();
-
- ///
- /// Converts the instance to string representation.
- ///
- ///
- /// A that represents this instance.
- ///
- [EditorBrowsable(EditorBrowsableState.Never)]
- public override string ToString() => base.ToString();
-
- ///
- /// Creates a new copy of the current , cloning its attributes into a new instance.
- ///
- ///
- /// A new copy of .
- ///
- internal abstract Retry Clone();
-
- ///
- /// Allows a concrete retry policy implementation to offer a base retry interval to be used in
- /// the calculations performed by .
- ///
- ///
- /// The last exception that was observed for the operation to be retried.
- /// The amount of time remaining for the cumulative timeout across retry attempts.
- /// The number of seconds to base the suggested retry interval on; this should be used as the minimum interval returned under normal circumstances.
- /// The number of retries that have already been attempted.
- ///
- /// The amount of time to delay before retrying the associated operation; if null, then the operation is no longer eligible to be retried.
- ///
- ///
- /// The interval produced by this method is considered non-authoritative and is subject to adjustment by the
- /// implementation.
- ///
- ///
- protected abstract TimeSpan? CalculateNextRetryInterval(Exception lastException,
- TimeSpan remainingTime,
- int baseWaitSeconds,
- int retryCount);
- }
-}
diff --git a/sdk/eventhub/Azure.Messaging.EventHubs/src/RetryMode.cs b/sdk/eventhub/Azure.Messaging.EventHubs/src/RetryMode.cs
new file mode 100755
index 0000000000000..798f3d6543b5a
--- /dev/null
+++ b/sdk/eventhub/Azure.Messaging.EventHubs/src/RetryMode.cs
@@ -0,0 +1,19 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+
+namespace Azure.Messaging.EventHubs
+{
+ ///
+ /// The type of approach to apply when calculating the delay
+ /// between retry attempts.
+ ///
+ ///
+ public enum RetryMode
+ {
+ /// Retry attempts happen at fixed intervals; each delay is a consistent duration.
+ Fixed,
+
+ /// Retry attempts will delay based on a backoff strategy, where each attempt will increase the duration that it waits before retrying.
+ Exponential
+ }
+}
diff --git a/sdk/eventhub/Azure.Messaging.EventHubs/src/RetryOptions.cs b/sdk/eventhub/Azure.Messaging.EventHubs/src/RetryOptions.cs
new file mode 100755
index 0000000000000..df0f1e4d4c54a
--- /dev/null
+++ b/sdk/eventhub/Azure.Messaging.EventHubs/src/RetryOptions.cs
@@ -0,0 +1,118 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+
+using System;
+using Azure.Messaging.EventHubs.Core;
+
+namespace Azure.Messaging.EventHubs
+{
+ ///
+ /// The set of options that can be specified to influence how
+ /// retry attempts are made, and a failure is eligible to be retried.
+ ///
+ ///
+ public class RetryOptions
+ {
+ /// The maximum number of retry attempts before considering the associated operation to have failed.
+ private int _maximumRetries = 3;
+
+ /// The delay or backoff factor to apply between retry attempts.
+ private TimeSpan _delay = TimeSpan.FromSeconds(0.8);
+
+ /// The maximum delay to allow between retry attempts.
+ private TimeSpan _maximumDelay = TimeSpan.FromMinutes(1);
+
+ /// The maximum duration to wait for an operation, per attempt.
+ private TimeSpan _tryTimeOut = TimeSpan.FromMinutes(1);
+
+ ///
+ /// The approach to use for calculating retry delays.
+ ///
+ ///
+ public RetryMode Mode { get; set; } = RetryMode.Exponential;
+
+ ///
+ /// The maximum number of retry attempts before considering the associated operation
+ /// to have failed.
+ ///
+ ///
+ public int MaximumRetries
+ {
+ get => _maximumRetries;
+
+ set
+ {
+ Guard.ArgumentInRange(nameof(MaximumRetries), value, 0, 100);
+ _maximumRetries = value;
+ }
+ }
+
+ ///
+ /// The delay between retry attempts for a fixed approach or the delay
+ /// on which to base calculations for a backoff-based approach.
+ ///
+ ///
+ public TimeSpan Delay
+ {
+ get => _delay;
+
+ set
+ {
+ Guard.ArgumentInRange(nameof(Delay), value, TimeSpan.Zero, TimeSpan.FromMinutes(5));
+ _delay = value;
+ }
+ }
+
+ ///
+ /// The maximum permissible delay between retry attempts.
+ ///
+ ///
+ public TimeSpan MaximumDelay
+ {
+ get => _maximumDelay;
+
+ set
+ {
+ Guard.ArgumentNotNegative(nameof(MaximumDelay), value);
+ _maximumDelay = value;
+ }
+ }
+
+ ///
+ /// The maximum duration to wait for completion of a single attempt, whether the intial
+ /// attempt or a retry.
+ ///
+ ///
+ public TimeSpan TryTimeout
+ {
+ get => _tryTimeOut;
+
+ set
+ {
+ if (value < TimeSpan.Zero)
+ {
+ throw new ArgumentException(Resources.TimeoutMustBePositive, nameof(TryTimeout));
+ }
+
+ Guard.ArgumentInRange(nameof(TryTimeout), value, TimeSpan.Zero, TimeSpan.FromHours(1));
+ _tryTimeOut = value;
+ }
+ }
+
+ ///
+ /// Creates a new copy of the current , cloning its attributes into a new instance.
+ ///
+ ///
+ /// A new copy of .
+ ///
+ internal RetryOptions Clone() =>
+ new RetryOptions
+ {
+ Mode = this.Mode,
+ _maximumRetries = this._maximumRetries,
+ _delay = this._delay,
+ _maximumDelay = this._maximumDelay,
+ _tryTimeOut = this._tryTimeOut
+ };
+ }
+}
diff --git a/sdk/eventhub/Azure.Messaging.EventHubs/src/TrackOneClient/Amqp/AmqpEventHubClient.cs b/sdk/eventhub/Azure.Messaging.EventHubs/src/TrackOneClient/Amqp/AmqpEventHubClient.cs
old mode 100644
new mode 100755
index ed06ce94f1ec5..b05e8ff89a3b9
--- a/sdk/eventhub/Azure.Messaging.EventHubs/src/TrackOneClient/Amqp/AmqpEventHubClient.cs
+++ b/sdk/eventhub/Azure.Messaging.EventHubs/src/TrackOneClient/Amqp/AmqpEventHubClient.cs
@@ -6,8 +6,8 @@ namespace TrackOne.Amqp
using System;
using System.Net;
using System.Threading.Tasks;
- using Microsoft.Azure.Amqp.Sasl;
using Microsoft.Azure.Amqp;
+ using Microsoft.Azure.Amqp.Sasl;
using Microsoft.Azure.Amqp.Transport;
using TrackOne.Amqp.Management;
diff --git a/sdk/eventhub/Azure.Messaging.EventHubs/src/TrackOneClient/Amqp/AmqpExceptionHelper.cs b/sdk/eventhub/Azure.Messaging.EventHubs/src/TrackOneClient/Amqp/AmqpExceptionHelper.cs
old mode 100644
new mode 100755
index df605307f863d..bc9035fb52f83
--- a/sdk/eventhub/Azure.Messaging.EventHubs/src/TrackOneClient/Amqp/AmqpExceptionHelper.cs
+++ b/sdk/eventhub/Azure.Messaging.EventHubs/src/TrackOneClient/Amqp/AmqpExceptionHelper.cs
@@ -59,7 +59,7 @@ public static AmqpSymbol GetResponseErrorCondition(AmqpMessage response, AmqpRes
public static Exception ToMessagingContract(Error error, bool connectionError = false)
{
return error == null ?
- new EventHubsException(true, "Unknown error.")
+ new EventHubsException(true, "Unknown error.")
: ToMessagingContract(error.Condition.Value, error.Description, connectionError);
}
diff --git a/sdk/eventhub/Azure.Messaging.EventHubs/src/TrackOneClient/Amqp/AmqpPartitionReceiver.cs b/sdk/eventhub/Azure.Messaging.EventHubs/src/TrackOneClient/Amqp/AmqpPartitionReceiver.cs
old mode 100644
new mode 100755
diff --git a/sdk/eventhub/Azure.Messaging.EventHubs/src/TrackOneClient/Amqp/SerializationUtilities.cs b/sdk/eventhub/Azure.Messaging.EventHubs/src/TrackOneClient/Amqp/SerializationUtilities.cs
old mode 100644
new mode 100755
index 66f37cdbe14be..b5bc2575f6bf4
--- a/sdk/eventhub/Azure.Messaging.EventHubs/src/TrackOneClient/Amqp/SerializationUtilities.cs
+++ b/sdk/eventhub/Azure.Messaging.EventHubs/src/TrackOneClient/Amqp/SerializationUtilities.cs
@@ -5,7 +5,7 @@ namespace TrackOne.Amqp
{
using System;
using System.Collections.Generic;
-
+
// WARNING: Consult filter engine owner before modifying this enum.
// Introducing a new member here has impact to filtering engine in data type precedence and data conversion.
// ALWASYS insert new types before Unknown!
diff --git a/sdk/eventhub/Azure.Messaging.EventHubs/src/TrackOneClient/EventHubClient.cs b/sdk/eventhub/Azure.Messaging.EventHubs/src/TrackOneClient/EventHubClient.cs
index a086d98d528e4..7b3eacf474676 100755
--- a/sdk/eventhub/Azure.Messaging.EventHubs/src/TrackOneClient/EventHubClient.cs
+++ b/sdk/eventhub/Azure.Messaging.EventHubs/src/TrackOneClient/EventHubClient.cs
@@ -5,12 +5,11 @@ namespace TrackOne
{
using System;
using System.Collections.Generic;
- using System.Diagnostics;
using System.Net;
using System.Threading.Tasks;
+ using Microsoft.IdentityModel.Clients.ActiveDirectory;
using TrackOne.Amqp;
using TrackOne.Primitives;
- using Microsoft.IdentityModel.Clients.ActiveDirectory;
///
/// Anchor class - all EventHub client operations start here.
diff --git a/sdk/eventhub/Azure.Messaging.EventHubs/src/TrackOneClient/EventHubsEventSource.cs b/sdk/eventhub/Azure.Messaging.EventHubs/src/TrackOneClient/EventHubsEventSource.cs
old mode 100644
new mode 100755
index 8fc38d0e8d18d..738371df1fadb
--- a/sdk/eventhub/Azure.Messaging.EventHubs/src/TrackOneClient/EventHubsEventSource.cs
+++ b/sdk/eventhub/Azure.Messaging.EventHubs/src/TrackOneClient/EventHubsEventSource.cs
@@ -128,7 +128,7 @@ public void ClientCreated(string clientId, string details)
WriteEvent(12, clientId, details ?? string.Empty);
}
}
-
+
[Event(13, Level = EventLevel.Informational, Message = "{0}: closing.")]
public void ClientCloseStart(string clientId)
{
@@ -137,7 +137,7 @@ public void ClientCloseStart(string clientId)
WriteEvent(13, clientId);
}
}
-
+
[Event(14, Level = EventLevel.Informational, Message = "{0}: closed.")]
public void ClientCloseStop(string clientId)
{
diff --git a/sdk/eventhub/Azure.Messaging.EventHubs/src/TrackOneClient/PartitionReceiver.cs b/sdk/eventhub/Azure.Messaging.EventHubs/src/TrackOneClient/PartitionReceiver.cs
index 0c5136de5997d..2f9d7f7fc5b75 100755
--- a/sdk/eventhub/Azure.Messaging.EventHubs/src/TrackOneClient/PartitionReceiver.cs
+++ b/sdk/eventhub/Azure.Messaging.EventHubs/src/TrackOneClient/PartitionReceiver.cs
@@ -6,7 +6,6 @@ namespace TrackOne
using System;
using System.Collections.Generic;
using System.Diagnostics;
- using System.Globalization;
using System.Text;
using System.Threading.Tasks;
diff --git a/sdk/eventhub/Azure.Messaging.EventHubs/src/TrackOneClient/PartitionSender.cs b/sdk/eventhub/Azure.Messaging.EventHubs/src/TrackOneClient/PartitionSender.cs
index 1f0f6aec75cfe..404dc7eec5e76 100755
--- a/sdk/eventhub/Azure.Messaging.EventHubs/src/TrackOneClient/PartitionSender.cs
+++ b/sdk/eventhub/Azure.Messaging.EventHubs/src/TrackOneClient/PartitionSender.cs
@@ -3,9 +3,7 @@
namespace TrackOne
{
- using System;
using System.Collections.Generic;
- using System.Diagnostics;
using System.Threading.Tasks;
using TrackOne.Primitives;
@@ -130,7 +128,7 @@ public async Task SendAsync(IEnumerable eventDatas)
throw Fx.Exception.InvalidOperation(Resources.PartitionSenderInvalidWithPartitionKeyOnBatch);
}
- await this.InnerSender.SendAsync(eventDatas, null).ConfigureAwait(false);
+ await this.InnerSender.SendAsync(eventDatas, null).ConfigureAwait(false);
}
///
diff --git a/sdk/eventhub/Azure.Messaging.EventHubs/src/TrackOneClient/Primitives/EventHubsConnectionStringBuilder.cs b/sdk/eventhub/Azure.Messaging.EventHubs/src/TrackOneClient/Primitives/EventHubsConnectionStringBuilder.cs
old mode 100644
new mode 100755
index 353ad57e0fc30..29e1bd2c139ef
--- a/sdk/eventhub/Azure.Messaging.EventHubs/src/TrackOneClient/Primitives/EventHubsConnectionStringBuilder.cs
+++ b/sdk/eventhub/Azure.Messaging.EventHubs/src/TrackOneClient/Primitives/EventHubsConnectionStringBuilder.cs
@@ -71,7 +71,7 @@ public EventHubsConnectionStringBuilder(
string entityPath,
string sharedAccessKeyName,
string sharedAccessKey)
- : this (endpointAddress, entityPath, sharedAccessKeyName, sharedAccessKey, ClientConstants.DefaultOperationTimeout)
+ : this(endpointAddress, entityPath, sharedAccessKeyName, sharedAccessKey, ClientConstants.DefaultOperationTimeout)
{
}
@@ -176,7 +176,7 @@ internal EventHubsConnectionStringBuilder(
///
/// Shared Access Signature
public string SharedAccessSignature { get; set; }
-
+
///
/// Get the entity path value from the connection string
///
diff --git a/sdk/eventhub/Azure.Messaging.EventHubs/src/TrackOneClient/Primitives/RetryExponential.cs b/sdk/eventhub/Azure.Messaging.EventHubs/src/TrackOneClient/Primitives/RetryExponential.cs
old mode 100644
new mode 100755
index 9b1b168be3da3..a80b3bf726d81
--- a/sdk/eventhub/Azure.Messaging.EventHubs/src/TrackOneClient/Primitives/RetryExponential.cs
+++ b/sdk/eventhub/Azure.Messaging.EventHubs/src/TrackOneClient/Primitives/RetryExponential.cs
@@ -12,10 +12,10 @@ namespace TrackOne
///
internal sealed class RetryExponential : RetryPolicy
{
- readonly TimeSpan minimumBackoff;
- readonly TimeSpan maximumBackoff;
- readonly int maximumRetryCount;
- readonly double retryFactor;
+ internal readonly TimeSpan minimumBackoff;
+ internal readonly TimeSpan maximumBackoff;
+ internal readonly int maximumRetryCount;
+ internal readonly double retryFactor;
///
/// Returns a new RetryExponential retry policy object.
diff --git a/sdk/eventhub/Azure.Messaging.EventHubs/src/TrackOneClient/Primitives/SecurityToken.cs b/sdk/eventhub/Azure.Messaging.EventHubs/src/TrackOneClient/Primitives/SecurityToken.cs
old mode 100644
new mode 100755
index c8d5c62a4febf..2c917f7bef8cb
--- a/sdk/eventhub/Azure.Messaging.EventHubs/src/TrackOneClient/Primitives/SecurityToken.cs
+++ b/sdk/eventhub/Azure.Messaging.EventHubs/src/TrackOneClient/Primitives/SecurityToken.cs
@@ -19,7 +19,7 @@ internal class SecurityToken
/// Expiry date-time
///
readonly DateTime expiresAtUtc;
-
+
///
/// Token audience
///
diff --git a/sdk/eventhub/Azure.Messaging.EventHubs/src/TrackOneClient/Primitives/TokenProvider.cs b/sdk/eventhub/Azure.Messaging.EventHubs/src/TrackOneClient/Primitives/TokenProvider.cs
index c617837295c51..2fbd954c30d50 100755
--- a/sdk/eventhub/Azure.Messaging.EventHubs/src/TrackOneClient/Primitives/TokenProvider.cs
+++ b/sdk/eventhub/Azure.Messaging.EventHubs/src/TrackOneClient/Primitives/TokenProvider.cs
@@ -5,8 +5,8 @@ namespace TrackOne
{
using System;
using System.Threading.Tasks;
- using TrackOne.Primitives;
using Microsoft.IdentityModel.Clients.ActiveDirectory;
+ using TrackOne.Primitives;
///
/// This abstract base class can be extended to implement additional token providers.
diff --git a/sdk/eventhub/Azure.Messaging.EventHubs/src/TrackOneClient/Resources.Designer.cs b/sdk/eventhub/Azure.Messaging.EventHubs/src/TrackOneClient/Resources.Designer.cs
index 512d3d23727f5..0ea426fe6f663 100755
--- a/sdk/eventhub/Azure.Messaging.EventHubs/src/TrackOneClient/Resources.Designer.cs
+++ b/sdk/eventhub/Azure.Messaging.EventHubs/src/TrackOneClient/Resources.Designer.cs
@@ -8,10 +8,8 @@
//
//------------------------------------------------------------------------------
-namespace TrackOne {
- using System;
-
-
+namespace TrackOne
+{
///
/// A strongly-typed resource class, for looking up localized strings, etc.
///
@@ -22,211 +20,257 @@ namespace TrackOne {
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "16.0.0.0")]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
- internal class Resources {
-
+ internal class Resources
+ {
+
private static global::System.Resources.ResourceManager resourceMan;
-
+
private static global::System.Globalization.CultureInfo resourceCulture;
-
+
[global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
- internal Resources() {
+ internal Resources()
+ {
}
-
+
///
/// Returns the cached ResourceManager instance used by this class.
///
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
- internal static global::System.Resources.ResourceManager ResourceManager {
- get {
- if (object.ReferenceEquals(resourceMan, null)) {
+ internal static global::System.Resources.ResourceManager ResourceManager
+ {
+ get
+ {
+ if (object.ReferenceEquals(resourceMan, null))
+ {
global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Azure.Messaging.EventHubs.TrackOneClient.Resources", typeof(Resources).Assembly);
resourceMan = temp;
}
return resourceMan;
}
}
-
+
///
/// Overrides the current thread's CurrentUICulture property for all
/// resource lookups using this strongly typed resource class.
///
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
- internal static global::System.Globalization.CultureInfo Culture {
- get {
+ internal static global::System.Globalization.CultureInfo Culture
+ {
+ get
+ {
return resourceCulture;
}
- set {
+ set
+ {
resourceCulture = value;
}
}
-
+
///
/// Looks up a localized string similar to The received message (delivery-id:{0}, size:{1} bytes) exceeds the limit ({2} bytes) currently allowed on the link..
///
- internal static string AmqpMessageSizeExceeded {
- get {
+ internal static string AmqpMessageSizeExceeded
+ {
+ get
+ {
return ResourceManager.GetString("AmqpMessageSizeExceeded", resourceCulture);
}
}
-
+
///
/// Looks up a localized string similar to Please make sure either all or none of the following arguments are defined: '{0},{1}'..
///
- internal static string ArgumentInvalidCombination {
- get {
+ internal static string ArgumentInvalidCombination
+ {
+ get
+ {
return ResourceManager.GetString("ArgumentInvalidCombination", resourceCulture);
}
}
-
+
///
/// Looks up a localized string similar to The argument {0} is null or white space..
///
- internal static string ArgumentNullOrWhiteSpace {
- get {
+ internal static string ArgumentNullOrWhiteSpace
+ {
+ get
+ {
return ResourceManager.GetString("ArgumentNullOrWhiteSpace", resourceCulture);
}
}
-
+
///
/// Looks up a localized string similar to The argument '{0}' cannot exceed {1} characters..
///
- internal static string ArgumentStringTooBig {
- get {
+ internal static string ArgumentStringTooBig
+ {
+ get
+ {
return ResourceManager.GetString("ArgumentStringTooBig", resourceCulture);
}
}
-
+
///
/// Looks up a localized string similar to Sending empty {0} is not a valid operation..
///
- internal static string CannotSendAnEmptyEvent {
- get {
+ internal static string CannotSendAnEmptyEvent
+ {
+ get
+ {
return ResourceManager.GetString("CannotSendAnEmptyEvent", resourceCulture);
}
}
-
+
///
/// Looks up a localized string similar to There are no event data supplied. Please make sure input events are not empty..
///
- internal static string EventDataListIsNullOrEmpty {
- get {
+ internal static string EventDataListIsNullOrEmpty
+ {
+ get
+ {
return ResourceManager.GetString("EventDataListIsNullOrEmpty", resourceCulture);
}
}
-
+
///
/// Looks up a localized string similar to Serialization operation failed due to unsupported type {0}..
///
- internal static string FailedToSerializeUnsupportedType {
- get {
+ internal static string FailedToSerializeUnsupportedType
+ {
+ get
+ {
return ResourceManager.GetString("FailedToSerializeUnsupportedType", resourceCulture);
}
}
-
+
///
/// Looks up a localized string similar to The string has an invalid encoding format..
///
- internal static string InvalidEncoding {
- get {
+ internal static string InvalidEncoding
+ {
+ get
+ {
return ResourceManager.GetString("InvalidEncoding", resourceCulture);
}
}
-
+
///
/// Looks up a localized string similar to System property '{0}' is missing in the event..
///
- internal static string MissingSystemProperty {
- get {
+ internal static string MissingSystemProperty
+ {
+ get
+ {
return ResourceManager.GetString("MissingSystemProperty", resourceCulture);
}
}
-
+
///
/// Looks up a localized string similar to EventData batch with partitionKey cannot be sent on PartitionSender..
///
- internal static string PartitionSenderInvalidWithPartitionKeyOnBatch {
- get {
+ internal static string PartitionSenderInvalidWithPartitionKeyOnBatch
+ {
+ get
+ {
return ResourceManager.GetString("PartitionSenderInvalidWithPartitionKeyOnBatch", resourceCulture);
}
}
-
+
///
/// Looks up a localized string similar to The {0} plugin has already been registered..
///
- internal static string PluginAlreadyRegistered {
- get {
+ internal static string PluginAlreadyRegistered
+ {
+ get
+ {
return ResourceManager.GetString("PluginAlreadyRegistered", resourceCulture);
}
}
-
+
///
/// Looks up a localized string similar to There was an error trying to register the {0} plugin..
///
- internal static string PluginRegistrationFailed {
- get {
+ internal static string PluginRegistrationFailed
+ {
+ get
+ {
return ResourceManager.GetString("PluginRegistrationFailed", resourceCulture);
}
}
-
+
///
/// Looks up a localized string similar to The 'identifier' parameter exceeds the maximum allowed size of {0} characters..
///
- internal static string ReceiverIdentifierOverMaxValue {
- get {
+ internal static string ReceiverIdentifierOverMaxValue
+ {
+ get
+ {
return ResourceManager.GetString("ReceiverIdentifierOverMaxValue", resourceCulture);
}
}
-
+
///
/// Looks up a localized string similar to {0} cannot be specified along with {1}. {0} alone should be sufficient to Authenticate the request..
///
- internal static string SasTokenShouldBeAlone {
- get {
+ internal static string SasTokenShouldBeAlone
+ {
+ get
+ {
return ResourceManager.GetString("SasTokenShouldBeAlone", resourceCulture);
}
}
-
+
///
/// Looks up a localized string similar to Argument {0} must be a non-negative timeout value. The provided value was {1}..
///
- internal static string TimeoutMustBeNonNegative {
- get {
+ internal static string TimeoutMustBeNonNegative
+ {
+ get
+ {
return ResourceManager.GetString("TimeoutMustBeNonNegative", resourceCulture);
}
}
-
+
///
/// Looks up a localized string similar to Argument {0} must be a positive timeout value. The provided value was {1}..
///
- internal static string TimeoutMustBePositive {
- get {
+ internal static string TimeoutMustBePositive
+ {
+ get
+ {
return ResourceManager.GetString("TimeoutMustBePositive", resourceCulture);
}
}
-
+
///
/// Looks up a localized string similar to The provided token does not specify the 'Audience' value..
///
- internal static string TokenMissingAudience {
- get {
+ internal static string TokenMissingAudience
+ {
+ get
+ {
return ResourceManager.GetString("TokenMissingAudience", resourceCulture);
}
}
-
+
///
/// Looks up a localized string similar to The provided token does not specify the 'ExpiresOn' value..
///
- internal static string TokenMissingExpiresOn {
- get {
+ internal static string TokenMissingExpiresOn
+ {
+ get
+ {
return ResourceManager.GetString("TokenMissingExpiresOn", resourceCulture);
}
}
-
+
///
/// Looks up a localized string similar to The value supplied must be between {0} and {1}..
///
- internal static string ValueOutOfRange {
- get {
+ internal static string ValueOutOfRange
+ {
+ get
+ {
return ResourceManager.GetString("ValueOutOfRange", resourceCulture);
}
}
diff --git a/sdk/eventhub/Azure.Messaging.EventHubs/tests/Authorization/EventHubSharedKeyCredentialTests.cs b/sdk/eventhub/Azure.Messaging.EventHubs/tests/Authorization/EventHubSharedKeyCredentialTests.cs
index 03b601cc0d932..bf73eae6e6382 100755
--- a/sdk/eventhub/Azure.Messaging.EventHubs/tests/Authorization/EventHubSharedKeyCredentialTests.cs
+++ b/sdk/eventhub/Azure.Messaging.EventHubs/tests/Authorization/EventHubSharedKeyCredentialTests.cs
@@ -14,7 +14,7 @@ namespace Azure.Messaging.EventHubs.Tests
///
///
[TestFixture]
- [Parallelizable(ParallelScope.Children)]
+ [Parallelizable(ParallelScope.All)]
public class EventHubSharedKeyCredentialTests
{
///
diff --git a/sdk/eventhub/Azure.Messaging.EventHubs/tests/Authorization/EventHubTokenCredentialTests.cs b/sdk/eventhub/Azure.Messaging.EventHubs/tests/Authorization/EventHubTokenCredentialTests.cs
index 5b1d2522ea898..e76cf7b8715f4 100755
--- a/sdk/eventhub/Azure.Messaging.EventHubs/tests/Authorization/EventHubTokenCredentialTests.cs
+++ b/sdk/eventhub/Azure.Messaging.EventHubs/tests/Authorization/EventHubTokenCredentialTests.cs
@@ -19,7 +19,7 @@ namespace Azure.Messaging.EventHubs.Tests
///
///
[TestFixture]
- [Parallelizable(ParallelScope.Children)]
+ [Parallelizable(ParallelScope.All)]
public class EventHubTokenCredentialTests
{
///
diff --git a/sdk/eventhub/Azure.Messaging.EventHubs/tests/Authorization/SharedAccessSignatureCredentialTests.cs b/sdk/eventhub/Azure.Messaging.EventHubs/tests/Authorization/SharedAccessSignatureCredentialTests.cs
index 6135d93bd760c..ba53873c94a29 100755
--- a/sdk/eventhub/Azure.Messaging.EventHubs/tests/Authorization/SharedAccessSignatureCredentialTests.cs
+++ b/sdk/eventhub/Azure.Messaging.EventHubs/tests/Authorization/SharedAccessSignatureCredentialTests.cs
@@ -15,7 +15,7 @@ namespace Azure.Messaging.EventHubs.Tests
///
///
[TestFixture]
- [Parallelizable(ParallelScope.Children)]
+ [Parallelizable(ParallelScope.All)]
public class SharedAccessSignatureCredentialTests
{
///
diff --git a/sdk/eventhub/Azure.Messaging.EventHubs/tests/Authorization/SharedAccessSignatureTests.cs b/sdk/eventhub/Azure.Messaging.EventHubs/tests/Authorization/SharedAccessSignatureTests.cs
index f40219fb969fe..f8565024f1ac3 100755
--- a/sdk/eventhub/Azure.Messaging.EventHubs/tests/Authorization/SharedAccessSignatureTests.cs
+++ b/sdk/eventhub/Azure.Messaging.EventHubs/tests/Authorization/SharedAccessSignatureTests.cs
@@ -15,7 +15,7 @@ namespace Azure.Messaging.EventHubs.Tests.Authorization
///
///
[TestFixture]
- [Parallelizable(ParallelScope.Children)]
+ [Parallelizable(ParallelScope.All)]
public class SharedAccessSignatureTests
{
/// A string that is 300 characters long, breaking invariants for argument maximum lengths.
diff --git a/sdk/eventhub/Azure.Messaging.EventHubs/tests/Compatibility/TrackOneComparerTests.cs b/sdk/eventhub/Azure.Messaging.EventHubs/tests/Compatibility/TrackOneComparerTests.cs
index 94856c867833b..f32318fbc9ec8 100755
--- a/sdk/eventhub/Azure.Messaging.EventHubs/tests/Compatibility/TrackOneComparerTests.cs
+++ b/sdk/eventhub/Azure.Messaging.EventHubs/tests/Compatibility/TrackOneComparerTests.cs
@@ -13,7 +13,7 @@ namespace Azure.Messaging.EventHubs.Tests
///
///
[TestFixture]
- [Parallelizable(ParallelScope.Children)]
+ [Parallelizable(ParallelScope.All)]
public class TrackOneComparerTests
{
///
diff --git a/sdk/eventhub/Azure.Messaging.EventHubs/tests/Compatibility/TrackOneEventHubClientTests.cs b/sdk/eventhub/Azure.Messaging.EventHubs/tests/Compatibility/TrackOneEventHubClientTests.cs
index 13c2f92e0f5d3..d39ce6ebba5dd 100755
--- a/sdk/eventhub/Azure.Messaging.EventHubs/tests/Compatibility/TrackOneEventHubClientTests.cs
+++ b/sdk/eventhub/Azure.Messaging.EventHubs/tests/Compatibility/TrackOneEventHubClientTests.cs
@@ -3,6 +3,7 @@
using System;
using System.Net;
+using System.Reflection;
using System.Threading;
using System.Threading.Tasks;
using Azure.Core;
@@ -22,7 +23,7 @@ namespace Azure.Messaging.EventHubs.Tests
///
///
[TestFixture]
- [Parallelizable(ParallelScope.Children)]
+ [Parallelizable(ParallelScope.All)]
public class TrackOneEventHubClientTests
{
///
@@ -34,7 +35,7 @@ public class TrackOneEventHubClientTests
[TestCase("")]
public void ConstructorRequiresTheHost(string host)
{
- Assert.That(() => new TrackOneEventHubClient(host, "test-path", Mock.Of(), new EventHubClientOptions()), Throws.InstanceOf());
+ Assert.That(() => new TrackOneEventHubClient(host, "test-path", Mock.Of(), new EventHubClientOptions(), Mock.Of()), Throws.InstanceOf());
}
///
@@ -46,7 +47,7 @@ public void ConstructorRequiresTheHost(string host)
[TestCase("")]
public void ConstructorRequiresTheEventHubPath(string path)
{
- Assert.That(() => new TrackOneEventHubClient("my.eventhub.com", path, Mock.Of(), new EventHubClientOptions()), Throws.InstanceOf());
+ Assert.That(() => new TrackOneEventHubClient("my.eventhub.com", path, Mock.Of(), new EventHubClientOptions(), Mock.Of()), Throws.InstanceOf());
}
///
@@ -56,7 +57,7 @@ public void ConstructorRequiresTheEventHubPath(string path)
[Test]
public void ConstructorRequiresTheCredential()
{
- Assert.That(() => new TrackOneEventHubClient("my.eventhub.com", "somePath", null, new EventHubClientOptions()), Throws.InstanceOf());
+ Assert.That(() => new TrackOneEventHubClient("my.eventhub.com", "somePath", null, new EventHubClientOptions(), Mock.Of()), Throws.InstanceOf());
}
///
@@ -64,9 +65,19 @@ public void ConstructorRequiresTheCredential()
///
///
[Test]
- public void ConstructorRequiresTheOption()
+ public void ConstructorRequiresTheOptions()
{
- Assert.That(() => new TrackOneEventHubClient("my.eventhub.com", "somePath", Mock.Of(), null), Throws.InstanceOf());
+ Assert.That(() => new TrackOneEventHubClient("my.eventhub.com", "somePath", Mock.Of(), null, Mock.Of()), Throws.InstanceOf());
+ }
+
+ ///
+ /// Verifies functionality of the constructor.
+ ///
+ ///
+ [Test]
+ public void ConstructorRequiresTheDefaultRetryPolicy()
+ {
+ Assert.That(() => new TrackOneEventHubClient("my.eventhub.com", "somePath", Mock.Of(), new EventHubClientOptions(), null), Throws.InstanceOf());
}
///
@@ -87,8 +98,9 @@ public void CreateFailsOnUnknownConnectionType()
var resource = $"amqps://{ host }/{ eventHubPath }";
var signature = new SharedAccessSignature(resource, "keyName", "KEY", TimeSpan.FromHours(1));
var credential = new SharedAccessSignatureCredential(signature);
+ var defaultRetry = Mock.Of();
- Assert.That(() => TrackOneEventHubClient.CreateClient(host, eventHubPath, credential, options), Throws.InstanceOf());
+ Assert.That(() => TrackOneEventHubClient.CreateClient(host, eventHubPath, credential, options, () => defaultRetry), Throws.InstanceOf());
}
///
@@ -104,7 +116,7 @@ public void CreateFailsForUnknownCredentialType()
var eventHubPath = "some-path";
var credential = Mock.Of();
- Assert.That(() => TrackOneEventHubClient.CreateClient(host, eventHubPath, credential, options), Throws.InstanceOf());
+ Assert.That(() => TrackOneEventHubClient.CreateClient(host, eventHubPath, credential, options, () => Mock.Of()), Throws.InstanceOf());
}
///
@@ -121,7 +133,8 @@ public async Task CreateClientCreatesTheProperClientType()
var resource = $"amqps://{ host }/{ eventHubPath }";
var signature = new SharedAccessSignature(resource, "keyName", "KEY", TimeSpan.FromHours(1));
var credential = new SharedAccessSignatureCredential(signature);
- var client = TrackOneEventHubClient.CreateClient(host, eventHubPath, credential, options);
+ var defaultRetry = Mock.Of();
+ var client = TrackOneEventHubClient.CreateClient(host, eventHubPath, credential, options, () => defaultRetry);
try
{
@@ -154,7 +167,8 @@ public async Task CreateClientTranslatesTheTransportType(TransportType connectio
var resource = $"amqps://{ host }/{ eventHubPath }";
var signature = new SharedAccessSignature(resource, "keyName", "KEY", TimeSpan.FromHours(1));
var credential = new SharedAccessSignatureCredential(signature);
- var client = (AmqpEventHubClient)TrackOneEventHubClient.CreateClient(host, eventHubPath, credential, options);
+ var defaultRetry = Mock.Of();
+ var client = (AmqpEventHubClient)TrackOneEventHubClient.CreateClient(host, eventHubPath, credential, options, () => defaultRetry);
try
{
@@ -188,7 +202,8 @@ public async Task CreateClientTranslatesTheSharedKeyCredential()
var resource = $"amqps://{ host }/{ eventHubPath }";
var signature = new SharedAccessSignature(resource, "keyName", "KEY", TimeSpan.FromHours(1));
var credential = new SharedAccessSignatureCredential(signature);
- var client = (AmqpEventHubClient)TrackOneEventHubClient.CreateClient(host, eventHubPath, credential, options);
+ var defaultRetry = Mock.Of();
+ var client = (AmqpEventHubClient)TrackOneEventHubClient.CreateClient(host, eventHubPath, credential, options, () => defaultRetry);
try
{
@@ -214,9 +229,9 @@ public async Task CreateClientTranslatesTheEventHubCredential()
var host = "my.eventhub.com";
var eventHubPath = "some-path";
var resource = $"amqps://{ host }/{ eventHubPath }";
- var signature = new SharedAccessSignature(resource, "keyName", "KEY", TimeSpan.FromHours(1));
var credential = new EventHubTokenCredential(Mock.Of(), resource);
- var client = (AmqpEventHubClient)TrackOneEventHubClient.CreateClient(host, eventHubPath, credential, options);
+ var defaultRetry = Mock.Of();
+ var client = (AmqpEventHubClient)TrackOneEventHubClient.CreateClient(host, eventHubPath, credential, options, () => defaultRetry);
try
{
@@ -244,7 +259,8 @@ public async Task CreateClientFormsTheCorrectEndpoint()
var resource = $"amqps://{ host }/{ eventHubPath }";
var signature = new SharedAccessSignature(resource, "keyName", "KEY", TimeSpan.FromHours(1));
var credential = new SharedAccessSignatureCredential(signature);
- var client = (AmqpEventHubClient)TrackOneEventHubClient.CreateClient(host, eventHubPath, credential, options);
+ var defaultRetry = Mock.Of();
+ var client = (AmqpEventHubClient)TrackOneEventHubClient.CreateClient(host, eventHubPath, credential, options, () => defaultRetry);
try
{
@@ -273,7 +289,8 @@ public async Task CreateClientPopulatesTheEventHubPath()
var resource = $"amqps://{ host }/{ eventHubPath }";
var signature = new SharedAccessSignature(resource, "keyName", "KEY", TimeSpan.FromHours(1));
var credential = new SharedAccessSignatureCredential(signature);
- var client = (AmqpEventHubClient)TrackOneEventHubClient.CreateClient(host, eventHubPath, credential, options);
+ var defaultRetry = Mock.Of();
+ var client = (AmqpEventHubClient)TrackOneEventHubClient.CreateClient(host, eventHubPath, credential, options, () => defaultRetry);
try
{
@@ -304,7 +321,8 @@ public async Task CreateClientPopulatesTheProxy()
var resource = $"amqps://{ host }/{ eventHubPath }";
var signature = new SharedAccessSignature(resource, "keyName", "KEY", TimeSpan.FromHours(1));
var credential = new SharedAccessSignatureCredential(signature);
- var client = (AmqpEventHubClient)TrackOneEventHubClient.CreateClient(host, eventHubPath, credential, options);
+ var defaultRetry = Mock.Of();
+ var client = (AmqpEventHubClient)TrackOneEventHubClient.CreateClient(host, eventHubPath, credential, options, () => defaultRetry);
try
{
@@ -316,6 +334,129 @@ public async Task CreateClientPopulatesTheProxy()
}
}
+ ///
+ /// Verifies functionality of the
+ /// method.
+ ///
+ ///
+ [Test]
+ public async Task CreateClientUsesClientOptionsForTheRetryPolicy()
+ {
+ var retryOptions = new RetryOptions
+ {
+ MaximumRetries = 99,
+ MaximumDelay = TimeSpan.FromHours(72),
+ Delay = TimeSpan.FromSeconds(27)
+ };
+
+ var options = new EventHubClientOptions
+ {
+ TransportType = TransportType.AmqpWebSockets,
+ RetryOptions = retryOptions
+ };
+
+ var host = "my.eventhub.com";
+ var eventHubPath = "some-path";
+ var resource = $"amqps://{ host }/{ eventHubPath }";
+ var signature = new SharedAccessSignature(resource, "keyName", "KEY", TimeSpan.FromHours(1));
+ var credential = new SharedAccessSignatureCredential(signature);
+ var client = (AmqpEventHubClient)TrackOneEventHubClient.CreateClient(host, eventHubPath, credential, options, () => Mock.Of());
+
+ try
+ {
+ Assert.That(client.RetryPolicy, Is.Not.Null, "The client should have a retry policy.");
+
+ var sourcePolicy = GetSourcePolicy((TrackOneRetryPolicy)client.RetryPolicy);
+ Assert.That(sourcePolicy, Is.InstanceOf(), "The source retry policy should be a basic retry policy.");
+
+ var clientPolicy = (BasicRetryPolicy)sourcePolicy;
+ Assert.That(clientPolicy.Options.Delay, Is.EqualTo(retryOptions.Delay), "The delays should match.");
+ Assert.That(clientPolicy.Options.MaximumDelay, Is.EqualTo(retryOptions.MaximumDelay), "The maximum delays should match.");
+ Assert.That(clientPolicy.Options.MaximumRetries, Is.EqualTo(retryOptions.MaximumRetries), "The maximum retries should match.");
+ Assert.That(clientPolicy.Options.TryTimeout, Is.EqualTo(retryOptions.TryTimeout), "The per-try timeouts should match.");
+ }
+ finally
+ {
+ await client?.CloseAsync();
+ }
+ }
+
+ ///
+ /// Verifies functionality of the
+ /// method.
+ ///
+ ///
+ [Test]
+ public async Task CreateClientUsesDefaultPolicyForTheRetryPolicy()
+ {
+ var retryOptions = new RetryOptions
+ {
+ MaximumRetries = 99,
+ MaximumDelay = TimeSpan.FromHours(72),
+ Delay = TimeSpan.FromSeconds(27)
+ };
+
+ var options = new EventHubClientOptions();
+ options.ClearRetryOptions();
+
+ var host = "my.eventhub.com";
+ var eventHubPath = "some-path";
+ var resource = $"amqps://{ host }/{ eventHubPath }";
+ var signature = new SharedAccessSignature(resource, "keyName", "KEY", TimeSpan.FromHours(1));
+ var credential = new SharedAccessSignatureCredential(signature);
+ var defaultRetryPolicy = new BasicRetryPolicy(retryOptions);
+ var client = (AmqpEventHubClient)TrackOneEventHubClient.CreateClient(host, eventHubPath, credential, options, () => defaultRetryPolicy);
+
+ try
+ {
+ Assert.That(client.RetryPolicy, Is.Not.Null, "The client should have a retry policy.");
+ Assert.That(client.RetryPolicy, Is.InstanceOf(), "The client should always use the track one compatibility retry policy.");
+
+ var clientPolicy = GetSourcePolicy((TrackOneRetryPolicy)client.RetryPolicy);
+ Assert.That(clientPolicy, Is.SameAs(defaultRetryPolicy), "The default policy should have been used as the source policy.");
+ }
+ finally
+ {
+ await client?.CloseAsync();
+ }
+ }
+
+ ///
+ /// Verifies functionality of the
+ /// method.
+ ///
+ ///
+ [Test]
+ public async Task CreateClientSetsTheOperationTimeout()
+ {
+ var retryOptions = new RetryOptions
+ {
+ TryTimeout = TimeSpan.FromMinutes(59)
+ };
+
+ var host = "http://my.eventhub.com";
+ var eventHubPath = "some-path";
+ var resource = $"amqps://{ host }/{ eventHubPath }";
+ var signature = new SharedAccessSignature(resource, "keyName", "KEY", TimeSpan.FromHours(1));
+ var credential = new SharedAccessSignatureCredential(signature);
+ var options = new EventHubClientOptions { RetryOptions = retryOptions };
+ var defaultRetryPolicy = new BasicRetryPolicy(retryOptions);
+ var mock = new ObservableClientMock(host, eventHubPath, credential, options);
+ var client = new TrackOneEventHubClient(host, eventHubPath, credential, options, defaultRetryPolicy, (host, path, credential, options, retry) => mock);
+
+ try
+ {
+ await client.GetPropertiesAsync(default);
+
+ var innerClient = GetTrackOneClient(client);
+ Assert.That(innerClient.ConnectionStringBuilder.OperationTimeout, Is.EqualTo(defaultRetryPolicy.CalculateTryTimeout(0)));
+ }
+ finally
+ {
+ await client?.CloseAsync(default);
+ }
+ }
+
///
/// Verifies functionality of the
/// method.
@@ -330,8 +471,9 @@ public async Task CloseAsyncDoesNotDelegateIfTheClientWasNotCreated()
var resource = $"amqps://{ host }/{ eventHubPath }";
var signature = new SharedAccessSignature(resource, "keyName", "KEY", TimeSpan.FromHours(1));
var credential = new SharedAccessSignatureCredential(signature);
+ var defaultRetry = Mock.Of();
var mock = new ObservableClientMock(host, eventHubPath, credential, options);
- var client = new TrackOneEventHubClient(host, eventHubPath, credential, options, (host, path, credential, options) => mock);
+ var client = new TrackOneEventHubClient(host, eventHubPath, credential, options, defaultRetry, (host, path, credential, options, retry) => mock);
await client.CloseAsync(default);
Assert.That(mock.WasCloseAsyncInvoked, Is.False);
@@ -351,8 +493,9 @@ public async Task CloseAsyncDelegatesToTheClient()
var resource = $"amqps://{ host }/{ eventHubPath }";
var signature = new SharedAccessSignature(resource, "keyName", "KEY", TimeSpan.FromHours(1));
var credential = new SharedAccessSignatureCredential(signature);
+ var defaultRetry = Mock.Of();
var mock = new ObservableClientMock(host, eventHubPath, credential, options);
- var client = new TrackOneEventHubClient(host, eventHubPath, credential, options, (host, path, credential, options) => mock);
+ var client = new TrackOneEventHubClient(host, eventHubPath, credential, options, defaultRetry, (host, path, credential, options, retry) => mock);
// Invoke an operation to force the client to be lazily instantiated. Otherwise,
// Close does not delegate the call.
@@ -376,8 +519,9 @@ public async Task GetPropertiesAsyncDelegatesToTheClient()
var resource = $"amqps://{ host }/{ eventHubPath }";
var signature = new SharedAccessSignature(resource, "keyName", "KEY", TimeSpan.FromHours(1));
var credential = new SharedAccessSignatureCredential(signature);
+ var defaultRetry = Mock.Of();
var mock = new ObservableClientMock(host, eventHubPath, credential, options);
- var client = new TrackOneEventHubClient(host, eventHubPath, credential, options, (host, path, credential, options) => mock);
+ var client = new TrackOneEventHubClient(host, eventHubPath, credential, options, defaultRetry, (host, path, credential, options, retry) => mock);
try
{
@@ -404,8 +548,9 @@ public async Task GetPartitionPropertiesAsyncDelegatesToTheClient()
var resource = $"amqps://{ host }/{ eventHubPath }";
var signature = new SharedAccessSignature(resource, "keyName", "KEY", TimeSpan.FromHours(1));
var credential = new SharedAccessSignatureCredential(signature);
+ var defaultRetry = Mock.Of();
var mock = new ObservableClientMock(host, eventHubPath, credential, options);
- var client = new TrackOneEventHubClient(host, eventHubPath, credential, options, (host, path, credential, options) => mock);
+ var client = new TrackOneEventHubClient(host, eventHubPath, credential, options, defaultRetry, (host, path, credential, options, retry) => mock);
try
{
@@ -434,8 +579,9 @@ public async Task CreateProducerDelegatesToTheClient()
var resource = $"amqps://{ host }/{ eventHubPath }";
var signature = new SharedAccessSignature(resource, "keyName", "KEY", TimeSpan.FromHours(1));
var credential = new SharedAccessSignatureCredential(signature);
+ var defaultRetry = Mock.Of();
var mock = new ObservableClientMock(host, eventHubPath, credential, options);
- var client = new TrackOneEventHubClient(host, eventHubPath, credential, options, (host, path, credential, options) => mock);
+ var client = new TrackOneEventHubClient(host, eventHubPath, credential, options, defaultRetry, (host, path, credential, options, retry) => mock);
try
{
@@ -444,7 +590,7 @@ public async Task CreateProducerDelegatesToTheClient()
// Because the producer is lazily instantiated, an operation needs to be requested to force creation. Because we are returning a null
// producer from within the mock client, that operation will fail with a null reference exception.
- Assert.That(async () => await client.CreateProducer(producerOptions)?.SendAsync(new[] { new EventData(new byte[] { 0x12 }) }), Throws.InstanceOf(), "because the EventDataSender was not populated.");
+ Assert.That(async () => await client.CreateProducer(producerOptions, defaultRetry)?.SendAsync(new[] { new EventData(new byte[] { 0x12 }) }), Throws.InstanceOf(), "because the EventDataSender was not populated.");
Assert.That(mock.CreateProducerInvokedWith, Is.EqualTo(producerOptions.PartitionId));
}
finally
@@ -453,6 +599,90 @@ public async Task CreateProducerDelegatesToTheClient()
}
}
+ ///
+ /// Verifies functionality of the
+ /// method.
+ ///
+ ///
+ [Test]
+ public async Task CreateProducerUsesRetryOptionsWhenProvided()
+ {
+ var options = new EventHubClientOptions();
+ var host = "http://my.eventhub.com";
+ var eventHubPath = "some-path";
+ var resource = $"amqps://{ host }/{ eventHubPath }";
+ var signature = new SharedAccessSignature(resource, "keyName", "KEY", TimeSpan.FromHours(1));
+ var credential = new SharedAccessSignatureCredential(signature);
+ var defaultRetry = Mock.Of();
+ var mock = new ObservableClientMock(host, eventHubPath, credential, options);
+ var client = new TrackOneEventHubClient(host, eventHubPath, credential, options, defaultRetry, (host, path, credential, options, retry) => mock);
+
+ try
+ {
+ var retryOptions = new RetryOptions
+ {
+ Delay = TimeSpan.FromSeconds(1),
+ MaximumDelay = TimeSpan.FromSeconds(2),
+ TryTimeout = TimeSpan.FromSeconds(3),
+ MaximumRetries = 99,
+ };
+
+ var producerOptions = new EventHubProducerOptions
+ {
+ PartitionId = "45345",
+ RetryOptions = retryOptions
+ };
+
+ var producer = client.CreateProducer(producerOptions, defaultRetry);
+ Assert.That(producer.RetryPolicy, Is.InstanceOf(), "The consumer should have been created using the options.");
+
+ var producerRetry = (BasicRetryPolicy)producer.RetryPolicy;
+ Assert.That(producerRetry.Options.Delay, Is.EqualTo(retryOptions.Delay), "The delays should match.");
+ Assert.That(producerRetry.Options.MaximumDelay, Is.EqualTo(retryOptions.MaximumDelay), "The maximum delays should match.");
+ Assert.That(producerRetry.Options.MaximumRetries, Is.EqualTo(retryOptions.MaximumRetries), "The maximum retries should match.");
+ Assert.That(producerRetry.Options.TryTimeout, Is.EqualTo(retryOptions.TryTimeout), "The per-try timeouts should match.");
+ }
+ finally
+ {
+ await client?.CloseAsync(default);
+ }
+ }
+
+ ///
+ /// Verifies functionality of the
+ /// method.
+ ///
+ ///
+ [Test]
+ public async Task CreateProducerUsesDefaultRetryPolicyWhenNoOptionsAreProvided()
+ {
+ var options = new EventHubClientOptions();
+ var host = "http://my.eventhub.com";
+ var eventHubPath = "some-path";
+ var resource = $"amqps://{ host }/{ eventHubPath }";
+ var signature = new SharedAccessSignature(resource, "keyName", "KEY", TimeSpan.FromHours(1));
+ var credential = new SharedAccessSignatureCredential(signature);
+ var defaultRetry = Mock.Of();
+ var mock = new ObservableClientMock(host, eventHubPath, credential, options);
+ var client = new TrackOneEventHubClient(host, eventHubPath, credential, options, defaultRetry, (host, path, credential, options, retry) => mock);
+
+ try
+ {
+ var producerOptions = new EventHubProducerOptions
+ {
+ PartitionId = "45345",
+ RetryOptions = null
+ };
+
+ var producer = client.CreateProducer(producerOptions, defaultRetry);
+ Assert.That(producer.RetryPolicy, Is.SameAs(defaultRetry));
+ }
+ finally
+ {
+ await client?.CloseAsync(default);
+ }
+ }
+
///
/// Verifies functionality of the
/// method.
@@ -467,8 +697,9 @@ public async Task CreateConsumerDelegatesToTheClient()
var resource = $"amqps://{ host }/{ eventHubPath }";
var signature = new SharedAccessSignature(resource, "keyName", "KEY", TimeSpan.FromHours(1));
var credential = new SharedAccessSignatureCredential(signature);
+ var defaultRetry = Mock.Of();
var mock = new ObservableClientMock(host, eventHubPath, credential, options);
- var client = new TrackOneEventHubClient(host, eventHubPath, credential, options, (host, path, credential, options) => mock);
+ var client = new TrackOneEventHubClient(host, eventHubPath, credential, options, defaultRetry, (host, path, credential, options, retry) => mock);
try
{
@@ -480,9 +711,9 @@ public async Task CreateConsumerDelegatesToTheClient()
// Because the consumer is lazily instantiated, an operation needs to be requested to force creation. Because we are returning a null
// consumer from within the mock client, that operation will fail with a null reference exception.
- Assert.That(async () => await client.CreateConsumer(consumerGroup, partitionId, eventPosition, consumerOptions).ReceiveAsync(10), Throws.InstanceOf(), "because the PartitionReceiver was not populated.");
+ Assert.That(async () => await client.CreateConsumer(consumerGroup, partitionId, eventPosition, consumerOptions, defaultRetry).ReceiveAsync(10), Throws.InstanceOf(), "because the PartitionReceiver was not populated.");
- (var calledConsumerGroup, var calledPartition, var calledPosition, var calledPriority, var calledOptions) = mock.CreateReiverInvokedWith;
+ (var calledConsumerGroup, var calledPartition, var calledPosition, var calledPriority, var calledOptions) = mock.CreateReceiverInvokedWith;
Assert.That(calledConsumerGroup, Is.EqualTo(consumerGroup), "The consumer group should match.");
Assert.That(calledPartition, Is.EqualTo(partitionId), "The partition should match.");
@@ -495,6 +726,258 @@ public async Task CreateConsumerDelegatesToTheClient()
}
}
+ ///
+ /// Verifies functionality of the
+ /// method.
+ ///
+ ///
+ [Test]
+ public async Task CreateConsumerUsesRetryOptionsWhenProvided()
+ {
+ var options = new EventHubClientOptions();
+ var host = "http://my.eventhub.com";
+ var eventHubPath = "some-path";
+ var resource = $"amqps://{ host }/{ eventHubPath }";
+ var signature = new SharedAccessSignature(resource, "keyName", "KEY", TimeSpan.FromHours(1));
+ var credential = new SharedAccessSignatureCredential(signature);
+ var defaultRetry = Mock.Of();
+ var mock = new ObservableClientMock(host, eventHubPath, credential, options);
+ var client = new TrackOneEventHubClient(host, eventHubPath, credential, options, defaultRetry, (host, path, credential, options, retry) => mock);
+
+ try
+ {
+ var partitionId = "32234";
+ var consumerGroup = "AGroup";
+ var eventPosition = EventPosition.FromOffset(34);
+
+ var retryOptions = new RetryOptions
+ {
+ Delay = TimeSpan.FromSeconds(1),
+ MaximumDelay = TimeSpan.FromSeconds(2),
+ TryTimeout = TimeSpan.FromSeconds(3),
+ MaximumRetries = 99,
+ };
+
+ var consumerOptions = new EventHubConsumerOptions
+ {
+ Identifier = "Test",
+ RetryOptions = retryOptions
+ };
+
+ var consumer = client.CreateConsumer(consumerGroup, partitionId, eventPosition, consumerOptions, defaultRetry);
+ Assert.That(consumer.RetryPolicy, Is.InstanceOf(), "The consumer should have been created using the options.");
+
+ var consumerRetry = (BasicRetryPolicy)consumer.RetryPolicy;
+ Assert.That(consumerRetry.Options.Delay, Is.EqualTo(retryOptions.Delay), "The delays should match.");
+ Assert.That(consumerRetry.Options.MaximumDelay, Is.EqualTo(retryOptions.MaximumDelay), "The maximum delays should match.");
+ Assert.That(consumerRetry.Options.MaximumRetries, Is.EqualTo(retryOptions.MaximumRetries), "The maximum retries should match.");
+ Assert.That(consumerRetry.Options.TryTimeout, Is.EqualTo(retryOptions.TryTimeout), "The per-try timeouts should match.");
+ }
+ finally
+ {
+ await client?.CloseAsync(default);
+ }
+ }
+
+ ///
+ /// Verifies functionality of the
+ /// method.
+ ///
+ ///
+ [Test]
+ public async Task CreateConsumerUsesDefaultRetryPolicyWhenNoOptionsAreProvided()
+ {
+ var options = new EventHubClientOptions();
+ var host = "http://my.eventhub.com";
+ var eventHubPath = "some-path";
+ var resource = $"amqps://{ host }/{ eventHubPath }";
+ var signature = new SharedAccessSignature(resource, "keyName", "KEY", TimeSpan.FromHours(1));
+ var credential = new SharedAccessSignatureCredential(signature);
+ var defaultRetry = Mock.Of();
+ var mock = new ObservableClientMock(host, eventHubPath, credential, options);
+ var client = new TrackOneEventHubClient(host, eventHubPath, credential, options, defaultRetry, (host, path, credential, options, retry) => mock);
+
+ try
+ {
+ var partitionId = "32234";
+ var consumerGroup = "AGroup";
+ var eventPosition = EventPosition.FromOffset(34);
+ var consumerOptions = new EventHubConsumerOptions
+ {
+ Identifier = "Test",
+ RetryOptions = null
+ };
+
+ var consumer = client.CreateConsumer(consumerGroup, partitionId, eventPosition, consumerOptions, defaultRetry);
+ Assert.That(consumer.RetryPolicy, Is.SameAs(defaultRetry));
+ }
+ finally
+ {
+ await client?.CloseAsync(default);
+ }
+ }
+
+ ///
+ /// Verifies functionality of the
+ /// method.
+ ///
+ ///
+ [Test]
+ public void RetryPolicyIsUpdatedWhenTheClientIsNotCreated()
+ {
+ var retryOptions = new RetryOptions
+ {
+ MaximumRetries = 99,
+ MaximumDelay = TimeSpan.FromHours(72),
+ Delay = TimeSpan.FromSeconds(27)
+ };
+
+ var host = "http://my.eventhub.com";
+ var eventHubPath = "some-path";
+ var resource = $"amqps://{ host }/{ eventHubPath }";
+ var signature = new SharedAccessSignature(resource, "keyName", "KEY", TimeSpan.FromHours(1));
+ var credential = new SharedAccessSignatureCredential(signature);
+ var options = new EventHubClientOptions();
+ var defaultRetryPolicy = new BasicRetryPolicy(retryOptions);
+ var newRetryPolicy = Mock.Of();
+ var mock = new ObservableClientMock(host, eventHubPath, credential, options);
+ var client = new TrackOneEventHubClient(host, eventHubPath, credential, options, defaultRetryPolicy, (host, path, credential, options, retry) => mock);
+
+ client.UpdateRetryPolicy(newRetryPolicy);
+
+ var activePolicy = GetRetryPolicy(client);
+ Assert.That(activePolicy, Is.SameAs(newRetryPolicy));
+ }
+
+ ///
+ /// Verifies functionality of the
+ /// method.
+ ///
+ ///
+ [Test]
+ public async Task RetryPolicyIsUpdatedWhenTheClientCreated()
+ {
+ var retryOptions = new RetryOptions
+ {
+ MaximumRetries = 99,
+ MaximumDelay = TimeSpan.FromHours(72),
+ Delay = TimeSpan.FromSeconds(27)
+ };
+
+ var host = "http://my.eventhub.com";
+ var eventHubPath = "some-path";
+ var resource = $"amqps://{ host }/{ eventHubPath }";
+ var signature = new SharedAccessSignature(resource, "keyName", "KEY", TimeSpan.FromHours(1));
+ var credential = new SharedAccessSignatureCredential(signature);
+ var options = new EventHubClientOptions();
+ var defaultRetryPolicy = new BasicRetryPolicy(retryOptions);
+ var newRetryPolicy = Mock.Of();
+ var mock = new ObservableClientMock(host, eventHubPath, credential, options);
+ var client = new TrackOneEventHubClient(host, eventHubPath, credential, options, defaultRetryPolicy, (host, path, credential, options, retry) => mock);
+
+ try
+ {
+ await client.GetPropertiesAsync(default);
+
+ client.UpdateRetryPolicy(newRetryPolicy);
+
+ var activePolicy = GetRetryPolicy(client);
+
+ Assert.That(activePolicy, Is.SameAs(newRetryPolicy), "The client's retry policy should be updated.");
+ Assert.That(mock.RetryPolicy, Is.TypeOf(), "The track one client retry policy should be a custom compatibility wrapper.");
+
+ var trackOnePolicy = GetSourcePolicy((TrackOneRetryPolicy)mock.RetryPolicy);
+ Assert.That(trackOnePolicy, Is.SameAs(newRetryPolicy), "The new retry policy should have been used as the source for the compatibility wrapper.");
+ }
+ finally
+ {
+ await client?.CloseAsync(default);
+ }
+ }
+
+ ///
+ /// Verifies functionality of the
+ /// method.
+ ///
+ ///
+ [Test]
+ public async Task OperationTimeoutIsUpdatedWhenTheClientCreated()
+ {
+ var retryOptions = new RetryOptions
+ {
+ TryTimeout = TimeSpan.FromMinutes(18)
+ };
+
+ var host = "http://my.eventhub.com";
+ var eventHubPath = "some-path";
+ var resource = $"amqps://{ host }/{ eventHubPath }";
+ var signature = new SharedAccessSignature(resource, "keyName", "KEY", TimeSpan.FromHours(1));
+ var credential = new SharedAccessSignatureCredential(signature);
+ var options = new EventHubClientOptions();
+ var defaultRetryPolicy = new BasicRetryPolicy(new RetryOptions());
+ var newRetryPolicy = new BasicRetryPolicy(retryOptions);
+ var mock = new ObservableClientMock(host, eventHubPath, credential, options);
+ var client = new TrackOneEventHubClient(host, eventHubPath, credential, options, defaultRetryPolicy, (host, path, credential, options, retry) => mock);
+
+ try
+ {
+ await client.GetPropertiesAsync(default);
+ client.UpdateRetryPolicy(newRetryPolicy);
+
+ var innerClient = GetTrackOneClient(client);
+ Assert.That(innerClient.ConnectionStringBuilder.OperationTimeout, Is.EqualTo(newRetryPolicy.CalculateTryTimeout(0)));
+ }
+ finally
+ {
+ await client?.CloseAsync(default);
+ }
+ }
+
+ ///
+ /// Gets the retry policy from a
+ /// by accessing its private field.
+ ///
+ ///
+ /// The client to retrieve the retry policy from.
+ ///
+ /// The retry policy
+ ///
+ private static EventHubRetryPolicy GetRetryPolicy(TrackOneEventHubClient client) =>
+ (EventHubRetryPolicy)
+ typeof(TrackOneEventHubClient)
+ .GetField("_retryPolicy", BindingFlags.Instance | BindingFlags.NonPublic)
+ .GetValue(client);
+
+ ///
+ /// Gets the retry policy used as the source of a
+ /// by accessing its private field.
+ ///
+ ///
+ /// The ploicy to retrieve the source policy from.
+ ///
+ /// The retry policy
+ ///
+ private static EventHubRetryPolicy GetSourcePolicy(TrackOneRetryPolicy policy) =>
+ (EventHubRetryPolicy)
+ typeof(TrackOneRetryPolicy)
+ .GetField("_sourcePolicy", BindingFlags.Instance | BindingFlags.NonPublic)
+ .GetValue(policy);
+
+ ///
+ /// Gets the track one Event Hub client used by a
+ /// by accessing its private field.
+ ///
+ ///
+ /// The client to retrieve the inner track one client from.
+ ///
+ /// The track one client
+ ///
+ private static TrackOne.EventHubClient GetTrackOneClient(TrackOneEventHubClient client) =>
+ (TrackOne.EventHubClient)
+ typeof(TrackOneEventHubClient)
+ .GetProperty("TrackOneClient", BindingFlags.Instance | BindingFlags.NonPublic)
+ .GetValue(client);
+
///
/// Allows for observation of operations performed by the client for testing purposes.
///
@@ -505,13 +988,13 @@ private class ObservableClientMock : TrackOne.EventHubClient
public bool WasGetRuntimeInvoked;
public string GetPartitionRuntimePartitionInvokedWith;
public string CreateProducerInvokedWith;
- public (string ConsumerGroup, string Partition, TrackOne.EventPosition startingPosition, long? priority, TrackOne.ReceiverOptions Options) CreateReiverInvokedWith;
+ public (string ConsumerGroup, string Partition, TrackOne.EventPosition StartingPosition, long? Priority, TrackOne.ReceiverOptions Options) CreateReceiverInvokedWith;
public ObservableClientMock(string host,
string path,
TokenCredential credential,
- EventHubClientOptions options) : base(new TrackOne.EventHubsConnectionStringBuilder(new Uri(host), path, "keyName", "KEY!"))
+ EventHubClientOptions options) : base(new TrackOne.EventHubsConnectionStringBuilder(new Uri(host), path, "keyName", "KEY!", options.RetryOptions.TryTimeout))
{
}
@@ -523,7 +1006,7 @@ protected override Task OnCloseAsync()
protected override PartitionReceiver OnCreateReceiver(string consumerGroupName, string partitionId, TrackOne.EventPosition eventPosition, long? epoch, TrackOne.ReceiverOptions consumerOptions)
{
- CreateReiverInvokedWith =
+ CreateReceiverInvokedWith =
(
consumerGroupName,
partitionId,
diff --git a/sdk/eventhub/Azure.Messaging.EventHubs/tests/Compatibility/TrackOneEventHubConsumerTests.cs b/sdk/eventhub/Azure.Messaging.EventHubs/tests/Compatibility/TrackOneEventHubConsumerTests.cs
index c73992a67e267..9b037c6cc62cd 100755
--- a/sdk/eventhub/Azure.Messaging.EventHubs/tests/Compatibility/TrackOneEventHubConsumerTests.cs
+++ b/sdk/eventhub/Azure.Messaging.EventHubs/tests/Compatibility/TrackOneEventHubConsumerTests.cs
@@ -4,10 +4,12 @@
using System;
using System.Collections.Generic;
using System.Linq;
+using System.Reflection;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Azure.Messaging.EventHubs.Compatibility;
+using Moq;
using NUnit.Framework;
using TrackOne;
@@ -19,7 +21,7 @@ namespace Azure.Messaging.EventHubs.Tests
///
///
[TestFixture]
- [Parallelizable(ParallelScope.Children)]
+ [Parallelizable(ParallelScope.All)]
public class TrackOneEventHubConsumerTests
{
///
@@ -29,7 +31,17 @@ public class TrackOneEventHubConsumerTests
[Test]
public void ConstructorValidatesTheReceiverFactory()
{
- Assert.That(() => new TrackOneEventHubConsumer(null), Throws.ArgumentNullException);
+ Assert.That(() => new TrackOneEventHubConsumer(null, Mock.Of()), Throws.ArgumentNullException);
+ }
+
+ ///
+ /// Verifies functionality of the constructor.
+ ///
+ ///
+ [Test]
+ public void ConstructorValidatesTheRetryPolicy()
+ {
+ Assert.That(() => new TrackOneEventHubConsumer(_ => default(TrackOne.PartitionReceiver), null), Throws.ArgumentNullException);
}
///
@@ -44,8 +56,9 @@ public async Task ReceiverIsConstructedCorrectly()
var position = EventPosition.FromEnqueuedTime(DateTimeOffset.Parse("2015-10-25T12:00:00Z"));
var priority = 8765;
var identifier = "ThisIsAnAwesomeConsumer!";
+ var retryPolicy = Mock.Of();
var mock = new ObservableReceiverMock(new ClientMock(), consumerGroup, partition, TrackOne.EventPosition.FromEnqueuedTime(position.EnqueuedTime.Value.UtcDateTime), priority, new ReceiverOptions { Identifier = identifier });
- var consumer = new TrackOneEventHubConsumer(() => mock);
+ var consumer = new TrackOneEventHubConsumer(_ => mock, retryPolicy);
// Invoke an operation to force the consumer to be lazily instantiated. Otherwise,
// construction does not happen.
@@ -57,6 +70,9 @@ public async Task ReceiverIsConstructedCorrectly()
Assert.That(TrackOneComparer.IsEventPositionEquivalent(mock.ConstructedWith.Position, position), Is.True, "The starting event position should match.");
Assert.That(mock.ConstructedWith.Priority, Is.EqualTo(priority), "The ownerlevel should match.");
Assert.That(mock.ConstructedWith.Options.Identifier, Is.EqualTo(identifier), "The consumer identifier should match.");
+
+ var consumerRetry = GetRetryPolicy(consumer);
+ Assert.That(consumerRetry, Is.SameAs(retryPolicy), "The consumer retry instance should match.");
}
///
@@ -69,7 +85,7 @@ public async Task ReceiveAsyncForwardsParameters()
var maximumEventCount = 666;
var maximumWaitTime = TimeSpan.FromMilliseconds(17);
var mock = new ObservableReceiverMock(new ClientMock(), "$Default", "0", TrackOne.EventPosition.FromEnd(), null, new ReceiverOptions());
- var consumer = new TrackOneEventHubConsumer(() => mock);
+ var consumer = new TrackOneEventHubConsumer(_ => mock, Mock.Of());
await consumer.ReceiveAsync(maximumEventCount, maximumWaitTime, CancellationToken.None);
@@ -85,7 +101,7 @@ public async Task ReceiveAsyncForwardsParameters()
public async Task ReceiveAsyncSendsAnEmptyEnumerableWhenThereAreNoEvents()
{
var mock = new ObservableReceiverMock(new ClientMock(), "$Default", "0", TrackOne.EventPosition.FromEnd(), null, new ReceiverOptions());
- var consumer = new TrackOneEventHubConsumer(() => mock);
+ var consumer = new TrackOneEventHubConsumer(_ => mock, Mock.Of());
var results = await consumer.ReceiveAsync(10, default, default);
Assert.That(results, Is.Not.Null, "There should have been an enumerable returned.");
@@ -100,7 +116,7 @@ public async Task ReceiveAsyncSendsAnEmptyEnumerableWhenThereAreNoEvents()
public async Task ReceiveAsyncTransformsResults()
{
var mock = new ObservableReceiverMock(new ClientMock(), "$Default", "0", TrackOne.EventPosition.FromEnd(), null, new ReceiverOptions());
- var consumer = new TrackOneEventHubConsumer(() => mock);
+ var consumer = new TrackOneEventHubConsumer(_ => mock, Mock.Of());
mock.ReceiveResult = new List
{
@@ -134,7 +150,7 @@ public async Task ReceiveAsyncTransformsResults()
public async Task CloseAsyncDoesNotDelegateIfTheReceiverWasNotCreated()
{
var mock = new ObservableReceiverMock(new ClientMock(), "$Default", "0", TrackOne.EventPosition.FromEnd(), null, new ReceiverOptions());
- var consumer = new TrackOneEventHubConsumer(() => mock);
+ var consumer = new TrackOneEventHubConsumer(_ => mock, Mock.Of());
await consumer.CloseAsync(default);
Assert.That(mock.WasCloseAsyncInvoked, Is.False);
@@ -149,7 +165,7 @@ public async Task CloseAsyncDoesNotDelegateIfTheReceiverWasNotCreated()
public async Task CloseAsyncDelegatesToTheReceiver()
{
var mock = new ObservableReceiverMock(new ClientMock(), "$Default", "0", TrackOne.EventPosition.FromEnd(), null, new ReceiverOptions());
- var consumer = new TrackOneEventHubConsumer(() => mock);
+ var consumer = new TrackOneEventHubConsumer(_ => mock, Mock.Of());
// Invoke an operation to force the consumer to be lazily instantiated. Otherwise,
// Close does not delegate the call.
@@ -159,15 +175,90 @@ public async Task CloseAsyncDelegatesToTheReceiver()
Assert.That(mock.WasCloseAsyncInvoked, Is.True);
}
+ ///
+ /// Verifies functionality of the
+ /// method.
+ ///
+ ///
+ [Test]
+ public void ProducerUpdatesTheRetryPolicyWhenTheSenderIsNotCreated()
+ {
+ var newRetryPolicy = Mock.Of();
+ var mock = new ObservableReceiverMock(new ClientMock(), "$Default", "0", TrackOne.EventPosition.FromEnd(), null, new ReceiverOptions());
+ var consumer = new TrackOneEventHubConsumer(_ => mock, newRetryPolicy);
+
+ consumer.UpdateRetryPolicy(newRetryPolicy);
+
+ var consumerRetry = GetRetryPolicy(consumer);
+ Assert.That(consumerRetry, Is.SameAs(newRetryPolicy), "The consumer retry instance should match.");
+ }
+
+ ///
+ /// Verifies functionality of the
+ /// method.
+ ///
+ ///
+ [Test]
+ public async Task ProduerUpdatesTheRetryPolicyWhenTheSenderIsCreated()
+ {
+ var newRetryPolicy = Mock.Of();
+ var mock = new ObservableReceiverMock(new ClientMock(), "$Default", "0", TrackOne.EventPosition.FromEnd(), null, new ReceiverOptions());
+ var consumer = new TrackOneEventHubConsumer(_ => mock, newRetryPolicy);
+
+ // Invoke an operation to force the consumer to be lazily instantiated. Otherwise,
+ // Close does not delegate the call.
+
+ await consumer.ReceiveAsync(0, TimeSpan.Zero, default);
+ consumer.UpdateRetryPolicy(newRetryPolicy);
+
+ var consumerRetry = GetRetryPolicy(consumer);
+ Assert.That(consumerRetry, Is.SameAs(newRetryPolicy), "The consumer retry instance should match.");
+ Assert.That(mock.RetryPolicy, Is.TypeOf(), "The track one client retry policy should be a custom compatibility wrapper.");
+
+ var trackOnePolicy = GetSourcePolicy((TrackOneRetryPolicy)mock.RetryPolicy);
+ Assert.That(trackOnePolicy, Is.SameAs(newRetryPolicy), "The new retry policy should have been used as the source for the compatibility wrapper.");
+ }
+
+ ///
+ /// Gets the retry policy from a
+ /// by accessing its private field.
+ ///
+ ///
+ /// The consumer to retrieve the retry policy from.
+ ///
+ /// The retry policy
+ ///
+ private static EventHubRetryPolicy GetRetryPolicy(TrackOneEventHubConsumer consumer) =>
+ (EventHubRetryPolicy)
+ typeof(TrackOneEventHubConsumer)
+ .GetField("_retryPolicy", BindingFlags.Instance | BindingFlags.NonPublic)
+ .GetValue(consumer);
+
+ ///
+ /// Gets the retry policy used as the source of a
+ /// by accessing its private field.
+ ///
+ ///
+ /// The ploicy to retrieve the source policy from.
+ ///
+ /// The retry policy
+ ///
+ private static EventHubRetryPolicy GetSourcePolicy(TrackOneRetryPolicy policy) =>
+ (EventHubRetryPolicy)
+ typeof(TrackOneRetryPolicy)
+ .GetField("_sourcePolicy", BindingFlags.Instance | BindingFlags.NonPublic)
+ .GetValue(policy);
+
+
///
/// Allows for observation of operations performed by the consumer for testing purposes.
///
///
- private class ObservableReceiverMock : TrackOne.PartitionReceiver
+ private class ObservableReceiverMock : PartitionReceiver
{
public bool WasCloseAsyncInvoked;
public (int MaxCount, TimeSpan WaitTime) ReceiveInvokeWith;
- public (string ConsumerGroup, string Partition, TrackOne.EventPosition Position, long? Priority, TrackOne.ReceiverOptions Options) ConstructedWith;
+ public (string ConsumerGroup, string Partition, TrackOne.EventPosition Position, long? Priority, ReceiverOptions Options) ConstructedWith;
public IList ReceiveResult = null;
@@ -176,7 +267,7 @@ public ObservableReceiverMock(TrackOne.EventHubClient eventHubClient,
string partitionId,
TrackOne.EventPosition eventPosition,
long? priority,
- TrackOne.ReceiverOptions options) : base(eventHubClient, consumerGroupName, partitionId, eventPosition, priority, options)
+ ReceiverOptions options) : base(eventHubClient, consumerGroupName, partitionId, eventPosition, priority, options)
{
ConstructedWith = (consumerGroupName, partitionId, eventPosition, priority, options);
}
diff --git a/sdk/eventhub/Azure.Messaging.EventHubs/tests/Compatibility/TrackOneEventHubProducerTests.cs b/sdk/eventhub/Azure.Messaging.EventHubs/tests/Compatibility/TrackOneEventHubProducerTests.cs
index 3371b31e14f4f..434ef17543014 100755
--- a/sdk/eventhub/Azure.Messaging.EventHubs/tests/Compatibility/TrackOneEventHubProducerTests.cs
+++ b/sdk/eventhub/Azure.Messaging.EventHubs/tests/Compatibility/TrackOneEventHubProducerTests.cs
@@ -4,10 +4,12 @@
using System;
using System.Collections.Generic;
using System.Linq;
+using System.Reflection;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Azure.Messaging.EventHubs.Compatibility;
+using Moq;
using NUnit.Framework;
using TrackOne;
@@ -19,7 +21,7 @@ namespace Azure.Messaging.EventHubs.Tests
///
///
[TestFixture]
- [Parallelizable(ParallelScope.Children)]
+ [Parallelizable(ParallelScope.All)]
public class TrackOneEventHubProducerTests
{
///
@@ -29,7 +31,17 @@ public class TrackOneEventHubProducerTests
[Test]
public void ConstructorValidatesTheProducerFactory()
{
- Assert.That(() => new TrackOneEventHubProducer(null), Throws.ArgumentNullException);
+ Assert.That(() => new TrackOneEventHubProducer(null, Mock.Of()), Throws.ArgumentNullException);
+ }
+
+ ///
+ /// Verifies functionality of the constructor.
+ ///
+ ///
+ [Test]
+ public void ConstructorValidatesTheRetryPolicy()
+ {
+ Assert.That(() => new TrackOneEventHubProducer(_ => default(TrackOne.EventDataSender), null), Throws.ArgumentNullException);
}
///
@@ -40,14 +52,18 @@ public void ConstructorValidatesTheProducerFactory()
public async Task ProducerIsConstructedCorrectly()
{
var partition = "123";
+ var retryPolicy = Mock.Of();
var mock = new ObservableSenderMock(new ClientMock(), partition);
- var producer = new TrackOneEventHubProducer(() => mock);
+ var producer = new TrackOneEventHubProducer(_ => mock, retryPolicy);
// Invoke an operation to force the producer to be lazily instantiated. Otherwise,
// construction does not happen.
await producer.SendAsync(new[] { new EventData(new byte[] { 0x12, 0x22 }) }, new SendOptions(), default);
Assert.That(mock.ConstructedWithPartition, Is.EqualTo(partition));
+
+ var producerRetry = GetRetryPolicy(producer);
+ Assert.That(producerRetry, Is.SameAs(retryPolicy), "The producer retry instance should match.");
}
///
@@ -64,7 +80,7 @@ public async Task SendAsyncForwardsThePartitionHashKey(string expectedHashKey)
{
var options = new SendOptions { PartitionKey = expectedHashKey };
var mock = new ObservableSenderMock(new ClientMock(), null);
- var producer = new TrackOneEventHubProducer(() => mock);
+ var producer = new TrackOneEventHubProducer(_ => mock, Mock.Of());
await producer.SendAsync(new[] { new EventData(new byte[] { 0x43 }) }, options, CancellationToken.None);
Assert.That(mock.SendCalledWithParameters, Is.Not.Null, "The Send request should have been delegated.");
@@ -90,7 +106,7 @@ public async Task SendAsyncTransformsSimpleEvents()
var options = new SendOptions();
var mock = new ObservableSenderMock(new ClientMock(), null);
- var producer = new TrackOneEventHubProducer(() => mock);
+ var producer = new TrackOneEventHubProducer(_ => mock, Mock.Of());
await producer.SendAsync(sourceEvents, options, CancellationToken.None);
Assert.That(mock.SendCalledWithParameters, Is.Not.Null, "The Send request should have been delegated.");
@@ -130,7 +146,7 @@ public async Task SendAsyncTransformsComplexEvents()
var options = new SendOptions();
var mock = new ObservableSenderMock(new ClientMock(), null);
- var producer = new TrackOneEventHubProducer(() => mock);
+ var producer = new TrackOneEventHubProducer(_ => mock, Mock.Of());
await producer.SendAsync(sourceEvents, options, CancellationToken.None);
Assert.That(mock.SendCalledWithParameters, Is.Not.Null, "The Send request should have been delegated.");
@@ -156,7 +172,7 @@ public async Task SendAsyncTransformsComplexEvents()
public async Task CloseAsyncDoesNotDelegateIfTheSenderWasNotCreated()
{
var mock = new ObservableSenderMock(new ClientMock(), null);
- var producer = new TrackOneEventHubProducer(() => mock);
+ var producer = new TrackOneEventHubProducer(_ => mock, Mock.Of());
await producer.CloseAsync(default);
Assert.That(mock.WasCloseAsyncInvoked, Is.False);
@@ -171,7 +187,7 @@ public async Task CloseAsyncDoesNotDelegateIfTheSenderWasNotCreated()
public async Task CloseAsyncDelegatesToTheSender()
{
var mock = new ObservableSenderMock(new ClientMock(), null);
- var producer = new TrackOneEventHubProducer(() => mock);
+ var producer = new TrackOneEventHubProducer(_ => mock, Mock.Of());
// Invoke an operation to force the producer to be lazily instantiated. Otherwise,
// Close does not delegate the call.
@@ -181,6 +197,80 @@ public async Task CloseAsyncDelegatesToTheSender()
Assert.That(mock.WasCloseAsyncInvoked, Is.True);
}
+ ///
+ /// Verifies functionality of the
+ /// method.
+ ///
+ ///
+ [Test]
+ public void ProducerUpdatesTheRetryPolicyWhenTheSenderIsNotCreated()
+ {
+ var newRetryPolicy = Mock.Of();
+ var mock = new ObservableSenderMock(new ClientMock(), null);
+ var producer = new TrackOneEventHubProducer(_ => mock, Mock.Of());
+
+ producer.UpdateRetryPolicy(newRetryPolicy);
+
+ var producerRetry = GetRetryPolicy(producer);
+ Assert.That(producerRetry, Is.SameAs(newRetryPolicy), "The producer retry instance should match.");
+ }
+
+ ///
+ /// Verifies functionality of the
+ /// method.
+ ///
+ ///
+ [Test]
+ public async Task ProduerUpdatesTheRetryPolicyWhenTheSenderIsCreated()
+ {
+ var newRetryPolicy = Mock.Of();
+ var mock = new ObservableSenderMock(new ClientMock(), null);
+ var producer = new TrackOneEventHubProducer(_ => mock, Mock.Of());
+
+ // Invoke an operation to force the producer to be lazily instantiated. Otherwise,
+ // Close does not delegate the call.
+
+ await producer.SendAsync(new[] { new EventData(new byte[] { 0x12, 0x22 }) }, new SendOptions(), default);
+ producer.UpdateRetryPolicy(newRetryPolicy);
+
+ var producerRetry = GetRetryPolicy(producer);
+ Assert.That(producerRetry, Is.SameAs(newRetryPolicy), "The producer retry instance should match.");
+ Assert.That(mock.RetryPolicy, Is.TypeOf(), "The track one client retry policy should be a custom compatibility wrapper.");
+
+ var trackOnePolicy = GetSourcePolicy((TrackOneRetryPolicy)mock.RetryPolicy);
+ Assert.That(trackOnePolicy, Is.SameAs(newRetryPolicy), "The new retry policy should have been used as the source for the compatibility wrapper.");
+ }
+
+ ///
+ /// Gets the retry policy from a
+ /// by accessing its private field.
+ ///
+ ///
+ /// The producer to retrieve the retry policy from.
+ ///
+ /// The retry policy
+ ///
+ private static EventHubRetryPolicy GetRetryPolicy(TrackOneEventHubProducer producer) =>
+ (EventHubRetryPolicy)
+ typeof(TrackOneEventHubProducer)
+ .GetField("_retryPolicy", BindingFlags.Instance | BindingFlags.NonPublic)
+ .GetValue(producer);
+
+ ///
+ /// Gets the retry policy used as the source of a
+ /// by accessing its private field.
+ ///
+ ///
+ /// The ploicy to retrieve the source policy from.
+ ///
+ /// The retry policy
+ ///
+ private static EventHubRetryPolicy GetSourcePolicy(TrackOneRetryPolicy policy) =>
+ (EventHubRetryPolicy)
+ typeof(TrackOneRetryPolicy)
+ .GetField("_sourcePolicy", BindingFlags.Instance | BindingFlags.NonPublic)
+ .GetValue(policy);
+
///
/// Allows for observation of operations performed by the producer for testing purposes.
///
diff --git a/sdk/eventhub/Azure.Messaging.EventHubs/tests/Compatibility/TrackOneExceptionExtensionsTests.cs b/sdk/eventhub/Azure.Messaging.EventHubs/tests/Compatibility/TrackOneExceptionExtensionsTests.cs
index f28aca2292ffd..28c50c92111c7 100755
--- a/sdk/eventhub/Azure.Messaging.EventHubs/tests/Compatibility/TrackOneExceptionExtensionsTests.cs
+++ b/sdk/eventhub/Azure.Messaging.EventHubs/tests/Compatibility/TrackOneExceptionExtensionsTests.cs
@@ -16,7 +16,7 @@ namespace Azure.Messaging.EventHubs.Tests
///
///
[TestFixture]
- [Parallelizable(ParallelScope.Children)]
+ [Parallelizable(ParallelScope.All)]
public class TrackOneExceptionExtensionsTests
{
///
@@ -30,7 +30,7 @@ public static IEnumerable
///
[TestFixture]
- [Parallelizable(ParallelScope.Children)]
+ [Parallelizable(ParallelScope.All)]
public class TrackOneGenericTokenProviderTests
{
///
diff --git a/sdk/eventhub/Azure.Messaging.EventHubs/tests/Compatibility/TrackOneGenericTokenTests.cs b/sdk/eventhub/Azure.Messaging.EventHubs/tests/Compatibility/TrackOneGenericTokenTests.cs
index fde6ff3a3f2f4..76eeb6a02badf 100755
--- a/sdk/eventhub/Azure.Messaging.EventHubs/tests/Compatibility/TrackOneGenericTokenTests.cs
+++ b/sdk/eventhub/Azure.Messaging.EventHubs/tests/Compatibility/TrackOneGenericTokenTests.cs
@@ -16,7 +16,7 @@ namespace Azure.Messaging.EventHubs.Tests
///
///
[TestFixture]
- [Parallelizable(ParallelScope.Children)]
+ [Parallelizable(ParallelScope.All)]
public class TrackOneGenericTokenTokenTests
{
///
diff --git a/sdk/eventhub/Azure.Messaging.EventHubs/tests/Compatibility/TrackOneRetryPolicyTests.cs b/sdk/eventhub/Azure.Messaging.EventHubs/tests/Compatibility/TrackOneRetryPolicyTests.cs
new file mode 100755
index 0000000000000..ad110f7524573
--- /dev/null
+++ b/sdk/eventhub/Azure.Messaging.EventHubs/tests/Compatibility/TrackOneRetryPolicyTests.cs
@@ -0,0 +1,126 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+
+using System;
+using Azure.Messaging.EventHubs.Compatibility;
+using Azure.Messaging.EventHubs.Errors;
+using Moq;
+using NUnit.Framework;
+
+namespace Azure.Messaging.EventHubs.Tests
+{
+ ///
+ /// The suite of tests for the
+ /// class.
+ ///
+ [TestFixture]
+ [Parallelizable(ParallelScope.All)]
+ public class TrackOneRetryPolicyTests
+ {
+ ///
+ /// Validates functionality of the constructor.
+ ///
+ ///
+ [Test]
+ public void ConstructorValidatesTheSourcePolicy()
+ {
+ Assert.That(() => new TrackOneRetryPolicy(null), Throws.ArgumentNullException);
+ }
+
+ ///
+ /// Verifies functionality of the
+ /// method.
+ ///
+ ///
+ [Test]
+ public void RetryIsNotInvokedForNonRetriableException()
+ {
+ var noRetryException = new Exception("NO RETRY!");
+ var retryPolicy = new TrackOneRetryPolicy(Mock.Of());
+
+ Assert.That(TrackOne.RetryPolicy.IsRetryableException(noRetryException), Is.False, "The base exception is considered as retriable by the TrackOne.RetryPolicy; this shouldn't be the case.");
+ Assert.That(retryPolicy.GetNextRetryInterval(noRetryException, TimeSpan.FromHours(4), 0), Is.Null);
+ }
+
+ ///
+ /// Verifies functionality of the
+ /// method.
+ ///
+ ///
+ [Test]
+ public void RetryIsInvokedForRetriableNonEventHubsException()
+ {
+ var retryCount = 99;
+ var lastException = new OperationCanceledException("RETRY!");
+ var expectedInterval = TimeSpan.FromMinutes(65);
+ var mockRetryPolicy = new Mock();
+ var retryPolicy = new TrackOneRetryPolicy(mockRetryPolicy.Object);
+
+ mockRetryPolicy
+ .Setup(policy => policy.CalculateRetryDelay(It.Is(value => Object.ReferenceEquals(value, lastException)), It.Is(value => value == retryCount)))
+ .Returns(expectedInterval);
+
+ Assert.That(TrackOne.RetryPolicy.IsRetryableException(lastException), Is.True, "The operation cancelled exception should be considered as retriable by the TrackOne.RetryPolicy.");
+ Assert.That(retryPolicy.GetNextRetryInterval(lastException, TimeSpan.FromHours(4), retryCount), Is.EqualTo(expectedInterval));
+ }
+
+ ///
+ /// Verifies functionality of the
+ /// method.
+ ///
+ ///
+ [Test]
+ public void RetryIsInvokedForRetriableTrackOneEventHubsException()
+ {
+ var retryCount = 99;
+ var lastException = new TrackOne.EventHubsTimeoutException("RETRY!");
+ var mappedException = lastException.MapToTrackTwoException();
+ var expectedInterval = TimeSpan.FromMinutes(65);
+ var mockRetryPolicy = new Mock();
+ var retryPolicy = new TrackOneRetryPolicy(mockRetryPolicy.Object);
+
+ mockRetryPolicy
+ .Setup(policy => policy.CalculateRetryDelay(It.Is(value => value.GetType() == mappedException.GetType()), It.Is(value => value == retryCount)))
+ .Returns(expectedInterval);
+
+ Assert.That(TrackOne.RetryPolicy.IsRetryableException(lastException), Is.True, "The timeoutd exception should be considered as retriable by the TrackOne.RetryPolicy.");
+ Assert.That(retryPolicy.GetNextRetryInterval(lastException, TimeSpan.FromHours(4), retryCount), Is.EqualTo(expectedInterval));
+ }
+
+ ///
+ /// Verifies functionality of the
+ /// method.
+ ///
+ ///
+ [Test]
+ public void RetryIsNotInvokedForWhenNoTimeRemains()
+ {
+ var lastException = new OperationCanceledException("RETRY!");
+ var retryPolicy = new TrackOneRetryPolicy(Mock.Of());
+
+ Assert.That(TrackOne.RetryPolicy.IsRetryableException(lastException), Is.True, "The operation cancelled exception should be considered as retriable by the TrackOne.RetryPolicy.");
+ Assert.That(retryPolicy.GetNextRetryInterval(lastException, TimeSpan.Zero, 0), Is.Null);
+ }
+
+ ///
+ /// Verifies functionality of the
+ /// method.
+ ///
+ ///
+ [Test]
+ public void RetryIntervalIsCancelledWhenLargerThanRemainingTime()
+ {
+ var retryCount = 99;
+ var lastException = new OperationCanceledException("RETRY!");
+ var mockRetryPolicy = new Mock();
+ var retryPolicy = new TrackOneRetryPolicy(mockRetryPolicy.Object);
+
+ mockRetryPolicy
+ .Setup(policy => policy.CalculateRetryDelay(It.Is(value => Object.ReferenceEquals(value, lastException)), It.Is(value => value == retryCount)))
+ .Returns(TimeSpan.FromHours(4));
+
+ Assert.That(TrackOne.RetryPolicy.IsRetryableException(lastException), Is.True, "The operation cancelled exception should be considered as retriable by the TrackOne.RetryPolicy.");
+ Assert.That(retryPolicy.GetNextRetryInterval(lastException, TimeSpan.FromSeconds(30), retryCount), Is.Null);
+ }
+ }
+}
diff --git a/sdk/eventhub/Azure.Messaging.EventHubs/tests/Compatibility/TrackOneSharedAccessSignatureTokenProviderTests.cs b/sdk/eventhub/Azure.Messaging.EventHubs/tests/Compatibility/TrackOneSharedAccessSignatureTokenProviderTests.cs
index 77e98ec9140fe..a397db3d69ecc 100755
--- a/sdk/eventhub/Azure.Messaging.EventHubs/tests/Compatibility/TrackOneSharedAccessSignatureTokenProviderTests.cs
+++ b/sdk/eventhub/Azure.Messaging.EventHubs/tests/Compatibility/TrackOneSharedAccessSignatureTokenProviderTests.cs
@@ -16,7 +16,7 @@ namespace Azure.Messaging.EventHubs.Tests
///
///
[TestFixture]
- [Parallelizable(ParallelScope.Children)]
+ [Parallelizable(ParallelScope.All)]
public class TrackOneSharedAccessSignatureTokenProviderTests
{
///
diff --git a/sdk/eventhub/Azure.Messaging.EventHubs/tests/Compatibility/TrackOneSharedAccessTokenTests.cs b/sdk/eventhub/Azure.Messaging.EventHubs/tests/Compatibility/TrackOneSharedAccessTokenTests.cs
index 1203d76b7f1ed..bf1935a3675ac 100755
--- a/sdk/eventhub/Azure.Messaging.EventHubs/tests/Compatibility/TrackOneSharedAccessTokenTests.cs
+++ b/sdk/eventhub/Azure.Messaging.EventHubs/tests/Compatibility/TrackOneSharedAccessTokenTests.cs
@@ -15,7 +15,7 @@ namespace Azure.Messaging.EventHubs.Tests
///
///
[TestFixture]
- [Parallelizable(ParallelScope.Children)]
+ [Parallelizable(ParallelScope.All)]
public class TrackOneSharedAccessSignatureTokenTests
{
///
diff --git a/sdk/eventhub/Azure.Messaging.EventHubs/tests/Core/ConnectionStringParserTests.cs b/sdk/eventhub/Azure.Messaging.EventHubs/tests/Core/ConnectionStringParserTests.cs
index 01ee3ace17c70..9f530e74c0138 100755
--- a/sdk/eventhub/Azure.Messaging.EventHubs/tests/Core/ConnectionStringParserTests.cs
+++ b/sdk/eventhub/Azure.Messaging.EventHubs/tests/Core/ConnectionStringParserTests.cs
@@ -14,7 +14,7 @@ namespace Azure.Messaging.EventHubs.Tests
///
///
[TestFixture]
- [Parallelizable(ParallelScope.Children)]
+ [Parallelizable(ParallelScope.All)]
public class ConnectionStringParserTests
{
///
diff --git a/sdk/eventhub/Azure.Messaging.EventHubs/tests/Core/ConnectionTypeExtensionTests.cs b/sdk/eventhub/Azure.Messaging.EventHubs/tests/Core/ConnectionTypeExtensionTests.cs
index a46d9923d686d..9c6a441f42030 100755
--- a/sdk/eventhub/Azure.Messaging.EventHubs/tests/Core/ConnectionTypeExtensionTests.cs
+++ b/sdk/eventhub/Azure.Messaging.EventHubs/tests/Core/ConnectionTypeExtensionTests.cs
@@ -13,7 +13,7 @@ namespace Azure.Messaging.EventHubs.Tests
///
///
[TestFixture]
- [Parallelizable(ParallelScope.Children)]
+ [Parallelizable(ParallelScope.All)]
public class ConnectionTypeExtensionTests
{
///
diff --git a/sdk/eventhub/Azure.Messaging.EventHubs/tests/Core/ExponentialRetryTests.cs b/sdk/eventhub/Azure.Messaging.EventHubs/tests/Core/ExponentialRetryTests.cs
deleted file mode 100755
index d71a0040cb596..0000000000000
--- a/sdk/eventhub/Azure.Messaging.EventHubs/tests/Core/ExponentialRetryTests.cs
+++ /dev/null
@@ -1,102 +0,0 @@
-using System;
-using System.Collections.Generic;
-using NUnit.Framework;
-
-namespace Azure.Messaging.EventHubs.Tests
-{
- ///
- /// The suite of tests for the
- /// class.
- ///
- ///
- [TestFixture]
- [Parallelizable(ParallelScope.Children)]
- public class ExponentialRetryTests
- {
- ///
- /// Provides the invalid test cases for the tests.
- ///
- ///
- public static IEnumerable
///
[TestFixture]
- [Parallelizable(ParallelScope.Children)]
+ [Parallelizable(ParallelScope.All)]
public class GuardTests
{
///
@@ -43,6 +43,30 @@ public static IEnumerable ArgumentNotNegativeForTimeSpanValidCases()
yield return new object[] { TimeSpan.FromTicks(1) };
}
+ ///
+ /// Provides the invalid test cases for the tests.
+ ///
+ ///
+ public static IEnumerable ArgumentInRangeForTimeSpanInvalidCases()
+ {
+ yield return new object[] { TimeSpan.FromMilliseconds(-1), TimeSpan.FromMilliseconds(0), TimeSpan.FromMilliseconds(10) };
+ yield return new object[] { TimeSpan.FromSeconds(-2), TimeSpan.FromMilliseconds(0), TimeSpan.FromMilliseconds(10) };
+ yield return new object[] { TimeSpan.FromSeconds(11), TimeSpan.FromSeconds(0), TimeSpan.FromSeconds(10) };
+ yield return new object[] { TimeSpan.FromSeconds(3), TimeSpan.FromSeconds(4), TimeSpan.FromSeconds(10) };
+ }
+
+ ///
+ /// Provides the valid test cases for the tests.
+ ///
+ ///
+ public static IEnumerable ArgumentInRangeForTimeSpanValidCases()
+ {
+ yield return new object[] { TimeSpan.FromMilliseconds(0), TimeSpan.FromMilliseconds(0), TimeSpan.FromMilliseconds(10) };
+ yield return new object[] { TimeSpan.FromSeconds(1), TimeSpan.FromMilliseconds(0), TimeSpan.FromSeconds(10) };
+ yield return new object[] { TimeSpan.FromSeconds(9), TimeSpan.FromMilliseconds(0), TimeSpan.FromSeconds(10) };
+ yield return new object[] { TimeSpan.FromHours(1), TimeSpan.FromHours(0), TimeSpan.FromHours(10) };
+ }
+
///
/// Verifies functionality of the method.
///
@@ -202,6 +226,32 @@ public void ArgumentInRangeAllowsValidValues(int value,
Assert.That(() => Guard.ArgumentInRange(nameof(value), value, minValue, maxValue), Throws.Nothing);
}
+ ///
+ /// Verifies functionality of the method.
+ ///
+ ///
+ [Test]
+ [TestCaseSource(nameof(ArgumentInRangeForTimeSpanInvalidCases))]
+ public void ArgumentInRangeForTimeSpanEnforcesInvariants(TimeSpan value,
+ TimeSpan minValue,
+ TimeSpan maxValue)
+ {
+ Assert.That(() => Guard.ArgumentInRange(nameof(value), value, minValue, maxValue), Throws.InstanceOf());
+ }
+
+ ///
+ /// Verifies functionality of the method.
+ ///
+ ///
+ [Test]
+ [TestCaseSource(nameof(ArgumentInRangeForTimeSpanValidCases))]
+ public void ArgumentInRangeForTimeSpanAllowsValidValues(TimeSpan value,
+ TimeSpan minValue,
+ TimeSpan maxValue)
+ {
+ Assert.That(() => Guard.ArgumentInRange(nameof(value), value, minValue, maxValue), Throws.Nothing);
+ }
+
///
/// Verifies functionality of the method.
///
diff --git a/sdk/eventhub/Azure.Messaging.EventHubs/tests/Errors/EventHubsExceptionTests.cs b/sdk/eventhub/Azure.Messaging.EventHubs/tests/Errors/EventHubsExceptionTests.cs
index 7ebd323da32b9..062612127071e 100755
--- a/sdk/eventhub/Azure.Messaging.EventHubs/tests/Errors/EventHubsExceptionTests.cs
+++ b/sdk/eventhub/Azure.Messaging.EventHubs/tests/Errors/EventHubsExceptionTests.cs
@@ -15,7 +15,7 @@ namespace Azure.Messaging.EventHubs.Tests
///
///
[TestFixture]
- [Parallelizable(ParallelScope.Children)]
+ [Parallelizable(ParallelScope.All)]
public class EventHubsExceptionTests
{
///
diff --git a/sdk/eventhub/Azure.Messaging.EventHubs/tests/EventHubClient/EventHubClientOptionsTests.cs b/sdk/eventhub/Azure.Messaging.EventHubs/tests/EventHubClient/EventHubClientOptionsTests.cs
index a16dbe0612b06..e11442ba67537 100755
--- a/sdk/eventhub/Azure.Messaging.EventHubs/tests/EventHubClient/EventHubClientOptionsTests.cs
+++ b/sdk/eventhub/Azure.Messaging.EventHubs/tests/EventHubClient/EventHubClientOptionsTests.cs
@@ -11,7 +11,7 @@ namespace Azure.Messaging.EventHubs.Tests
///
///
[TestFixture]
- [Parallelizable(ParallelScope.Children)]
+ [Parallelizable(ParallelScope.All)]
public class EventHubClientOptionsTests
{
///
@@ -24,9 +24,8 @@ public void CloneProducesACopy()
{
var options = new EventHubClientOptions
{
- Retry = new ExponentialRetry(TimeSpan.FromSeconds(1), TimeSpan.FromSeconds(2), 3),
+ RetryOptions = new RetryOptions { MaximumRetries = 27 },
TransportType = TransportType.AmqpWebSockets,
- DefaultTimeout = TimeSpan.FromDays(1),
Proxy = Mock.Of()
};
@@ -34,11 +33,10 @@ public void CloneProducesACopy()
Assert.That(clone, Is.Not.Null, "The clone should not be null.");
Assert.That(clone.TransportType, Is.EqualTo(options.TransportType), "The connection type of the clone should match.");
- Assert.That(clone.DefaultTimeout, Is.EqualTo(options.DefaultTimeout), "The default timeout of the clone should match.");
Assert.That(clone.Proxy, Is.EqualTo(options.Proxy), "The proxy of the clone should match.");
- Assert.That(ExponentialRetry.HaveSameConfiguration((ExponentialRetry)clone.Retry, (ExponentialRetry)options.Retry), Is.True, "The retry of the clone should be considered equal.");
- Assert.That(clone.Retry, Is.Not.SameAs(options.Retry), "The retry of the clone should be a copy, not the same instance.");
+ Assert.That(clone.RetryOptions.IsEquivalentTo(options.RetryOptions), Is.True, "The retry options of the clone should be considered equal.");
+ Assert.That(clone.RetryOptions, Is.Not.SameAs(options.RetryOptions), "The retry options of the clone should be a copy, not the same instance.");
}
///
@@ -47,36 +45,9 @@ public void CloneProducesACopy()
///
///
[Test]
- public void RetryIsValidated()
+ public void RetryOptionsIsValidated()
{
- Assert.That(() => new EventHubClientOptions { Retry = null }, Throws.ArgumentException);
- }
-
- ///
- /// Verifies functionality of the
- /// property.
- ///
- ///
- [Test]
- public void DefaultTimeoutIsValidated()
- {
- Assert.That(() => new EventHubClientOptions { DefaultTimeout = TimeSpan.FromMilliseconds(-1) }, Throws.ArgumentException);
- }
-
- ///
- /// Verifies functionality of the
- /// property.
- ///
- ///
- [Test]
- public void DefaultTimeoutUsesDefaultValueIfNotSpecified()
- {
- var options = new EventHubClientOptions();
- var defaultTimeoutValue = options.TimeoutOrDefault;
-
- options.DefaultTimeout = TimeSpan.Zero;
- Assert.That(options.DefaultTimeout, Is.EqualTo(TimeSpan.Zero), "The value supplied by the caller should be preserved.");
- Assert.That(options.TimeoutOrDefault, Is.EqualTo(defaultTimeoutValue), "The timeout value should be defaulted internally.");
+ Assert.That(() => new EventHubClientOptions { RetryOptions = null }, Throws.ArgumentException);
}
}
}
diff --git a/sdk/eventhub/Azure.Messaging.EventHubs/tests/EventHubClient/EventHubClientTests.cs b/sdk/eventhub/Azure.Messaging.EventHubs/tests/EventHubClient/EventHubClientTests.cs
index 1c5ee28e1cb8a..f80c76c976e6a 100755
--- a/sdk/eventhub/Azure.Messaging.EventHubs/tests/EventHubClient/EventHubClientTests.cs
+++ b/sdk/eventhub/Azure.Messaging.EventHubs/tests/EventHubClient/EventHubClientTests.cs
@@ -22,7 +22,7 @@ namespace Azure.Messaging.EventHubs.Tests
///
///
[TestFixture]
- [Parallelizable(ParallelScope.Children)]
+ [Parallelizable(ParallelScope.All)]
public class EventHubClientTests
{
///
@@ -64,8 +64,7 @@ public static IEnumerable ConstructorClonesOptionsCases()
var options = new EventHubClientOptions
{
TransportType = TransportType.AmqpWebSockets,
- DefaultTimeout = TimeSpan.FromHours(2),
- Retry = new ExponentialRetry(TimeSpan.FromMinutes(10), TimeSpan.FromHours(1), 24),
+ RetryOptions = new RetryOptions { MaximumRetries = 88, TryTimeout = TimeSpan.FromMinutes(58) },
Proxy = Mock.Of()
};
@@ -183,9 +182,8 @@ public void ConstructorCreatesDefaultOptions(ReadableOptionsMock client,
Assert.That(options, Is.Not.Null, $"The { constructorDescription } constructor should have set default options.");
Assert.That(options, Is.Not.SameAs(defaultOptions), $"The { constructorDescription } constructor should not have the same options instance.");
Assert.That(options.TransportType, Is.EqualTo(defaultOptions.TransportType), $"The { constructorDescription } constructor should have the correct connection type.");
- Assert.That(options.DefaultTimeout, Is.EqualTo(defaultOptions.DefaultTimeout), $"The { constructorDescription } constructor should have the correct default timeout.");
Assert.That(options.Proxy, Is.EqualTo(defaultOptions.Proxy), $"The { constructorDescription } constructor should have the correct proxy.");
- Assert.That(ExponentialRetry.HaveSameConfiguration((ExponentialRetry)options.Retry, (ExponentialRetry)defaultOptions.Retry), $"The { constructorDescription } constructor should have the correct retry.");
+ Assert.That(options.RetryOptions.IsEquivalentTo(defaultOptions.RetryOptions), Is.True, $"The { constructorDescription } constructor should have the correct retry options.");
}
///
@@ -204,9 +202,8 @@ public void ConstructorClonesOptions(ReadableOptionsMock client,
Assert.That(options, Is.Not.Null, $"The { constructorDescription } constructor should have set the options.");
Assert.That(options, Is.Not.SameAs(constructorOptions), $"The { constructorDescription } constructor should have cloned the options.");
Assert.That(options.TransportType, Is.EqualTo(constructorOptions.TransportType), $"The { constructorDescription } constructor should have the correct connection type.");
- Assert.That(options.DefaultTimeout, Is.EqualTo(constructorOptions.DefaultTimeout), $"The { constructorDescription } constructor should have the correct default timeout.");
Assert.That(options.Proxy, Is.EqualTo(constructorOptions.Proxy), $"The { constructorDescription } constructor should have the correct proxy.");
- Assert.That(ExponentialRetry.HaveSameConfiguration((ExponentialRetry)options.Retry, (ExponentialRetry)constructorOptions.Retry), $"The { constructorDescription } constructor should have the correct retry.");
+ Assert.That(options.RetryOptions.IsEquivalentTo(constructorOptions.RetryOptions), Is.True, $"The { constructorDescription } constructor should have the correct retry options.");
}
///
@@ -330,9 +327,8 @@ public void TransportClientReceivesDefaultOptions(ReadableOptionsMock client,
Assert.That(options, Is.Not.Null, $"The { constructorDescription } constructor should have set default options.");
Assert.That(options, Is.Not.SameAs(defaultOptions), $"The { constructorDescription } constructor should not have the same options instance.");
Assert.That(options.TransportType, Is.EqualTo(defaultOptions.TransportType), $"The { constructorDescription } constructor should have the correct connection type.");
- Assert.That(options.DefaultTimeout, Is.EqualTo(defaultOptions.DefaultTimeout), $"The { constructorDescription } constructor should have the correct default timeout.");
Assert.That(options.Proxy, Is.EqualTo(defaultOptions.Proxy), $"The { constructorDescription } constructor should have the correct proxy.");
- Assert.That(ExponentialRetry.HaveSameConfiguration((ExponentialRetry)options.Retry, (ExponentialRetry)defaultOptions.Retry), $"The { constructorDescription } constructor should have the correct retry.");
+ Assert.That(options.RetryOptions.IsEquivalentTo(defaultOptions.RetryOptions), Is.True, $"The { constructorDescription } constructor should have the correct retry.");
}
///
@@ -351,9 +347,8 @@ public void TransportClientReceivesClonedOptions(ReadableOptionsMock client,
Assert.That(options, Is.Not.Null, $"The { constructorDescription } constructor should have set the options.");
Assert.That(options, Is.Not.SameAs(constructorOptions), $"The { constructorDescription } constructor should have cloned the options.");
Assert.That(options.TransportType, Is.EqualTo(constructorOptions.TransportType), $"The { constructorDescription } constructor should have the correct connection type.");
- Assert.That(options.DefaultTimeout, Is.EqualTo(constructorOptions.DefaultTimeout), $"The { constructorDescription } constructor should have the correct default timeout.");
Assert.That(options.Proxy, Is.EqualTo(constructorOptions.Proxy), $"The { constructorDescription } constructor should have the correct proxy.");
- Assert.That(ExponentialRetry.HaveSameConfiguration((ExponentialRetry)options.Retry, (ExponentialRetry)constructorOptions.Retry), $"The { constructorDescription } constructor should have the correct retry.");
+ Assert.That(options.RetryOptions.IsEquivalentTo(constructorOptions.RetryOptions), Is.True, $"The { constructorDescription } constructor should have the correct retry.");
}
///
@@ -375,7 +370,7 @@ public void BuildTransportClientAllowsLegalConnectionTypes(TransportType connect
var credential = new SharedAccessSignatureCredential(signature);
var client = new EventHubClient(host, path, credential);
- Assert.That(() => client.BuildTransportClient(host, path, credential, options), Throws.Nothing);
+ Assert.That(() => client.BuildTransportClient(host, path, credential, options, client.RetryPolicy), Throws.Nothing);
}
///
@@ -397,7 +392,7 @@ public void BuildTransportClientRejectsInvalidConnectionTypes()
var credential = new SharedAccessSignatureCredential(signature);
var client = new EventHubClient(host, path, credential);
- Assert.That(() => client.BuildTransportClient(host, path, credential, options), Throws.InstanceOf());
+ Assert.That(() => client.BuildTransportClient(host, path, credential, options, client.RetryPolicy), Throws.InstanceOf());
}
///
@@ -408,16 +403,21 @@ public void BuildTransportClientRejectsInvalidConnectionTypes()
[Test]
public void CreateProducerCreatesDefaultWhenNoOptionsArePassed()
{
+ var retryOptions = new RetryOptions
+ {
+ MaximumRetries = 99,
+ MaximumDelay = TimeSpan.FromHours(72),
+ Delay = TimeSpan.FromSeconds(27)
+ };
+
var clientOptions = new EventHubClientOptions
{
- Retry = new ExponentialRetry(TimeSpan.FromSeconds(1), TimeSpan.FromSeconds(3), 5),
- DefaultTimeout = TimeSpan.FromHours(24)
+ RetryOptions = retryOptions
};
var expected = new EventHubProducerOptions
{
- Retry = clientOptions.Retry,
- Timeout = clientOptions.DefaultTimeout
+ RetryOptions = retryOptions
};
var connectionString = "Endpoint=value.com;SharedAccessKeyName=[value];SharedAccessKey=[value];EntityPath=[value]";
@@ -427,8 +427,7 @@ public void CreateProducerCreatesDefaultWhenNoOptionsArePassed()
Assert.That(mockClient.ProducerOptions, Is.Not.Null, "The producer options should have been set.");
Assert.That(mockClient.ProducerOptions.PartitionId, Is.EqualTo(expected.PartitionId), "The partition identifiers should match.");
- Assert.That(ExponentialRetry.HaveSameConfiguration((ExponentialRetry)mockClient.ProducerOptions.Retry, (ExponentialRetry)expected.Retry), "The retries should match.");
- Assert.That(mockClient.ProducerOptions.TimeoutOrDefault, Is.EqualTo(expected.TimeoutOrDefault), "The timeouts should match.");
+ Assert.That(mockClient.ProducerOptions.RetryOptions.IsEquivalentTo(expected.RetryOptions), Is.True, "The retries should match.");
}
///
@@ -437,38 +436,28 @@ public void CreateProducerCreatesDefaultWhenNoOptionsArePassed()
///
///
[Test]
- public void CreateProducerCreatesDefaultWhenOptionsAreNotSet()
+ public void CreateProducerReceivesTheDefaultRetryPolicy()
{
- var clientOptions = new EventHubClientOptions
+ var retryOptions = new RetryOptions
{
- Retry = new ExponentialRetry(TimeSpan.FromSeconds(1), TimeSpan.FromSeconds(3), 5),
- DefaultTimeout = TimeSpan.FromHours(24)
+ MaximumRetries = 99,
+ MaximumDelay = TimeSpan.FromHours(72),
+ Delay = TimeSpan.FromSeconds(27)
};
- var producerOptions = new EventHubProducerOptions
- {
- PartitionId = "123",
- Retry = null,
- Timeout = TimeSpan.Zero
- };
-
- var expected = new EventHubProducerOptions
+ var clientOptions = new EventHubClientOptions
{
- PartitionId = producerOptions.PartitionId,
- Retry = clientOptions.Retry,
- Timeout = clientOptions.DefaultTimeout
+ RetryOptions = retryOptions
};
var connectionString = "Endpoint=value.com;SharedAccessKeyName=[value];SharedAccessKey=[value];EntityPath=[value]";
var mockClient = new ReadableOptionsMock(connectionString, clientOptions);
- mockClient.CreateProducer(producerOptions);
+ mockClient.CreateProducer();
- Assert.That(mockClient.ProducerOptions, Is.Not.Null, "The producer options should have been set.");
- Assert.That(mockClient.ProducerOptions, Is.Not.SameAs(producerOptions), "The options should have been cloned.");
- Assert.That(mockClient.ProducerOptions.PartitionId, Is.EqualTo(expected.PartitionId), "The partition identifiers should match.");
- Assert.That(ExponentialRetry.HaveSameConfiguration((ExponentialRetry)mockClient.ProducerOptions.Retry, (ExponentialRetry)expected.Retry), "The retries should match.");
- Assert.That(mockClient.ProducerOptions.TimeoutOrDefault, Is.EqualTo(expected.TimeoutOrDefault), "The timeouts should match.");
+ Assert.That(mockClient.RetryPolicy, Is.Not.Null, "The client should have a retry policy set.");
+ Assert.That(mockClient.ProducerDefaultRetry, Is.Not.Null, "The producer should have received a default retry policy.");
+ Assert.That(mockClient.ProducerDefaultRetry, Is.SameAs(mockClient.RetryPolicy), "The client retry policy should have been used as the default.");
}
///
@@ -479,17 +468,21 @@ public void CreateProducerCreatesDefaultWhenOptionsAreNotSet()
[Test]
public void CreateConsumerCreatesDefaultWhenNoOptionsArePassed()
{
+ var retryOptions = new RetryOptions
+ {
+ MaximumRetries = 99,
+ MaximumDelay = TimeSpan.FromHours(72),
+ Delay = TimeSpan.FromSeconds(27)
+ };
+
var clientOptions = new EventHubClientOptions
{
- Retry = new ExponentialRetry(TimeSpan.FromSeconds(1), TimeSpan.FromSeconds(3), 5),
- DefaultTimeout = TimeSpan.FromHours(24)
+ RetryOptions = retryOptions
};
var expectedOptions = new EventHubConsumerOptions
{
- Retry = clientOptions.Retry,
- DefaultMaximumReceiveWaitTime = clientOptions.DefaultTimeout
-
+ RetryOptions = retryOptions
};
var expectedConsumerGroup = EventHubConsumer.DefaultConsumerGroupName;
@@ -505,7 +498,7 @@ public void CreateConsumerCreatesDefaultWhenNoOptionsArePassed()
Assert.That(actualOptions.OwnerLevel, Is.EqualTo(expectedOptions.OwnerLevel), "The owner levels should match.");
Assert.That(actualOptions.Identifier, Is.EqualTo(expectedOptions.Identifier), "The identifiers should match.");
Assert.That(actualOptions.PrefetchCount, Is.EqualTo(expectedOptions.PrefetchCount), "The prefetch counts should match.");
- Assert.That(ExponentialRetry.HaveSameConfiguration((ExponentialRetry)actualOptions.Retry, (ExponentialRetry)expectedOptions.Retry), "The retries should match.");
+ Assert.That(actualOptions.RetryOptions.IsEquivalentTo(expectedOptions.RetryOptions), Is.True, "The retries should match.");
Assert.That(actualOptions.MaximumReceiveWaitTimeOrDefault, Is.EqualTo(expectedOptions.MaximumReceiveWaitTimeOrDefault), "The wait times should match.");
}
@@ -517,10 +510,16 @@ public void CreateConsumerCreatesDefaultWhenNoOptionsArePassed()
[Test]
public void CreateConsumerCreatesDefaultWhenOptionsAreNotSet()
{
+ var retryOptions = new RetryOptions
+ {
+ MaximumRetries = 99,
+ MaximumDelay = TimeSpan.FromHours(72),
+ Delay = TimeSpan.FromSeconds(27)
+ };
+
var clientOptions = new EventHubClientOptions
{
- Retry = new ExponentialRetry(TimeSpan.FromSeconds(1), TimeSpan.FromSeconds(3), 5),
- DefaultTimeout = TimeSpan.FromHours(24)
+ RetryOptions = retryOptions
};
var expectedOptions = new EventHubConsumerOptions
@@ -528,9 +527,8 @@ public void CreateConsumerCreatesDefaultWhenOptionsAreNotSet()
OwnerLevel = 251,
Identifier = "Bob",
PrefetchCount = 600,
- Retry = clientOptions.Retry,
- DefaultMaximumReceiveWaitTime = clientOptions.DefaultTimeout
-
+ RetryOptions = retryOptions,
+ DefaultMaximumReceiveWaitTime = TimeSpan.FromSeconds(123)
};
var expectedConsumerGroup = "SomeGroup";
@@ -547,10 +545,40 @@ public void CreateConsumerCreatesDefaultWhenOptionsAreNotSet()
Assert.That(actualOptions.OwnerLevel, Is.EqualTo(expectedOptions.OwnerLevel), "The owner levels should match.");
Assert.That(actualOptions.Identifier, Is.EqualTo(expectedOptions.Identifier), "The identifiers should match.");
Assert.That(actualOptions.PrefetchCount, Is.EqualTo(expectedOptions.PrefetchCount), "The prefetch counts should match.");
- Assert.That(ExponentialRetry.HaveSameConfiguration((ExponentialRetry)actualOptions.Retry, (ExponentialRetry)expectedOptions.Retry), "The retries should match.");
+ Assert.That(actualOptions.RetryOptions.IsEquivalentTo(expectedOptions.RetryOptions), Is.True, "The retries should match.");
Assert.That(actualOptions.MaximumReceiveWaitTimeOrDefault, Is.EqualTo(expectedOptions.MaximumReceiveWaitTimeOrDefault), "The wait times should match.");
}
+ ///
+ /// Verifies functionality of the
+ /// method.
+ ///
+ ///
+ [Test]
+ public void CreateConsumerReceivesTheDefaultRetryPolicy()
+ {
+ var retryOptions = new RetryOptions
+ {
+ MaximumRetries = 99,
+ MaximumDelay = TimeSpan.FromHours(72),
+ Delay = TimeSpan.FromSeconds(27)
+ };
+
+ var clientOptions = new EventHubClientOptions
+ {
+ RetryOptions = retryOptions
+ };
+
+ var connectionString = "Endpoint=value.com;SharedAccessKeyName=[value];SharedAccessKey=[value];EntityPath=[value]";
+ var mockClient = new ReadableOptionsMock(connectionString, clientOptions);
+
+ mockClient.CreateConsumer("bleh", "1", EventPosition.Earliest);
+
+ Assert.That(mockClient.RetryPolicy, Is.Not.Null, "The client should have a retry policy set.");
+ Assert.That(mockClient.ConsumerDefaultRetry, Is.Not.Null, "The consumer should have received a default retry policy.");
+ Assert.That(mockClient.ConsumerDefaultRetry, Is.SameAs(mockClient.RetryPolicy), "The client retry policy should have been used as the default.");
+ }
+
///
/// Verifies functionality of the
/// method.
@@ -649,6 +677,41 @@ public async Task GetPartitionIdsAsyncDelegatesToGetProperties()
mockClient.VerifyAll();
}
+ ///
+ /// Verifies functionality of the
+ /// setter.
+ ///
+ ///
+ [Test]
+ public void SettingTheRetryPolicyUpdatesState()
+ {
+ var connectionString = "Endpoint=value.com;SharedAccessKeyName=[value];SharedAccessKey=[value];EntityPath=[value]";
+ var clientOptions = new EventHubClientOptions();
+ var mockClient = new ReadableOptionsMock(connectionString, clientOptions);
+
+ var newRetry = Mock.Of();
+ mockClient.RetryPolicy = newRetry;
+
+ Assert.That(mockClient.RetryPolicy, Is.SameAs(newRetry), "The client should have the correct retry policy set.");
+ Assert.That(mockClient.ClientOptions.RetryOptions, Is.Null, "The retry options should have been cleared when a new retry policy is set.");
+ }
+
+ ///
+ /// Verifies functionality of the
+ /// setter.
+ ///
+ ///
+ [Test]
+ public void SettingTheRetryPolicyUpdatesTheTransportClient()
+ {
+ var newRetry = Mock.Of();
+ var transportClient = new ObservableTransportClientMock();
+ var client = new InjectableTransportClientMock(transportClient, "Endpoint=sb://not-real.servicebus.windows.net/;SharedAccessKeyName=DummyKey;SharedAccessKey=[not_real];EntityPath=fake");
+
+ client.RetryPolicy = newRetry;
+ Assert.That(transportClient.UpdateRetryPolicyCalledWith, Is.SameAs(newRetry), "The retry policy should have been passed as the update.");
+ }
+
///
/// Verifies functionality of the
/// method.
@@ -692,15 +755,15 @@ public void CreateProducerInvokesTheTransportClient()
{
var transportClient = new ObservableTransportClientMock();
var client = new InjectableTransportClientMock(transportClient, "Endpoint=sb://not-real.servicebus.windows.net/;SharedAccessKeyName=DummyKey;SharedAccessKey=[not_real];EntityPath=fake");
- var expectedOptions = new EventHubProducerOptions { Retry = Retry.Default };
+ var expectedOptions = new EventHubProducerOptions { RetryOptions = new RetryOptions { MaximumRetries = 6, TryTimeout = TimeSpan.FromMinutes(4) } };
client.CreateProducer(expectedOptions);
- var actualOptions = transportClient.CreateProducerCalledWithOptions;
+ (var actualOptions, var actualDefaultRetry) = transportClient.CreateProducerCalledWith;
Assert.That(actualOptions, Is.Not.Null, "The producer options should have been set.");
Assert.That(actualOptions.PartitionId, Is.EqualTo(expectedOptions.PartitionId), "The partition identifiers should match.");
- Assert.That(ExponentialRetry.HaveSameConfiguration((ExponentialRetry)actualOptions.Retry, (ExponentialRetry)expectedOptions.Retry), "The retries should match.");
- Assert.That(actualOptions.TimeoutOrDefault, Is.EqualTo(expectedOptions.TimeoutOrDefault), "The timeouts should match.");
+ Assert.That(actualOptions.RetryOptions.IsEquivalentTo(expectedOptions.RetryOptions), Is.True, "The retry options should match.");
+ Assert.That(actualDefaultRetry, Is.SameAs(client.RetryPolicy), "The client retry policy should have been used as the default.");
}
///
@@ -713,13 +776,13 @@ public void CreateConsumerInvokesTheTransportClient()
{
var transportClient = new ObservableTransportClientMock();
var client = new InjectableTransportClientMock(transportClient, "Endpoint=sb://not-real.servicebus.windows.net/;SharedAccessKeyName=DummyKey;SharedAccessKey=[not_real];EntityPath=fake");
- var expectedOptions = new EventHubConsumerOptions { Retry = Retry.Default };
+ var expectedOptions = new EventHubConsumerOptions { RetryOptions = new RetryOptions { MaximumRetries = 67 } };
var expectedPosition = EventPosition.FromOffset(65);
var expectedPartition = "2123";
var expectedConsumerGroup = EventHubConsumer.DefaultConsumerGroupName;
client.CreateConsumer(expectedConsumerGroup, expectedPartition, expectedPosition, expectedOptions);
- (var actualConsumerGroup, var actualPartition, var actualPosition, var actualOptions) = transportClient.CreateConsumerCalledWith;
+ (var actualConsumerGroup, var actualPartition, var actualPosition, var actualOptions, var actualDefaultRetry) = transportClient.CreateConsumerCalledWith;
Assert.That(actualPartition, Is.EqualTo(expectedPartition), "The partition should have been passed.");
Assert.That(actualConsumerGroup, Is.EqualTo(expectedConsumerGroup), "The consumer groups should match.");
@@ -729,8 +792,8 @@ public void CreateConsumerInvokesTheTransportClient()
Assert.That(actualOptions.OwnerLevel, Is.EqualTo(expectedOptions.OwnerLevel), "The owner levels should match.");
Assert.That(actualOptions.Identifier, Is.EqualTo(expectedOptions.Identifier), "The identifiers should match.");
Assert.That(actualOptions.PrefetchCount, Is.EqualTo(expectedOptions.PrefetchCount), "The prefetch counts should match.");
- Assert.That(ExponentialRetry.HaveSameConfiguration((ExponentialRetry)actualOptions.Retry, (ExponentialRetry)expectedOptions.Retry), "The retries should match.");
Assert.That(actualOptions.MaximumReceiveWaitTimeOrDefault, Is.EqualTo(expectedOptions.MaximumReceiveWaitTimeOrDefault), "The wait times should match.");
+ Assert.That(actualDefaultRetry, Is.SameAs(client.RetryPolicy), "The client retry policy should have been used as the default.");
}
///
@@ -854,8 +917,10 @@ public class ReadableOptionsMock : EventHubClient
.GetValue(this) as EventHubClientOptions;
public EventHubClientOptions TransportClientOptions;
- public EventHubProducerOptions ProducerOptions => _transportClient.CreateProducerCalledWithOptions;
+ public EventHubProducerOptions ProducerOptions => _transportClient.CreateProducerCalledWith.Options;
+ public EventHubRetryPolicy ProducerDefaultRetry => _transportClient.CreateProducerCalledWith.DefaultRetry;
public EventHubConsumerOptions ConsumerOptions => _transportClient.CreateConsumerCalledWith.Options;
+ public EventHubRetryPolicy ConsumerDefaultRetry => _transportClient.CreateConsumerCalledWith.DefaultRetry;
private ObservableTransportClientMock _transportClient;
@@ -871,7 +936,7 @@ public ReadableOptionsMock(string host,
{
}
- internal override TransportEventHubClient BuildTransportClient(string host, string eventHubPath, TokenCredential credential, EventHubClientOptions options)
+ internal override TransportEventHubClient BuildTransportClient(string host, string eventHubPath, TokenCredential credential, EventHubClientOptions options, EventHubRetryPolicy defaultRetry)
{
TransportClientOptions = options;
_transportClient = new ObservableTransportClientMock();
@@ -934,7 +999,11 @@ public InjectableTransportClientMock(TransportEventHubClient transportClient,
SetTransportClient(transportClient);
}
- internal override TransportEventHubClient BuildTransportClient(string host, string eventHubPath, TokenCredential credential, EventHubClientOptions options) => TransportClient;
+ internal override TransportEventHubClient BuildTransportClient(string host,
+ string eventHubPath,
+ TokenCredential credential,
+ EventHubClientOptions options,
+ EventHubRetryPolicy defaultRetry) => TransportClient;
private void SetTransportClient(TransportEventHubClient transportClient) =>
typeof(EventHubClient)
@@ -949,8 +1018,9 @@ private void SetTransportClient(TransportEventHubClient transportClient) =>
///
private class ObservableTransportClientMock : TransportEventHubClient
{
- public (string ConsumerGroup, string Partition, EventPosition position, EventHubConsumerOptions Options) CreateConsumerCalledWith;
- public EventHubProducerOptions CreateProducerCalledWithOptions;
+ public (string ConsumerGroup, string Partition, EventPosition Position, EventHubConsumerOptions Options, EventHubRetryPolicy DefaultRetry) CreateConsumerCalledWith;
+ public (EventHubProducerOptions Options, EventHubRetryPolicy DefaultRetry) CreateProducerCalledWith;
+ public EventHubRetryPolicy UpdateRetryPolicyCalledWith;
public string GetPartitionPropertiesCalledForId;
public bool WasGetPropertiesCalled;
public bool WasCloseCalled;
@@ -968,18 +1038,28 @@ public override Task GetPartitionPropertiesAsync(string par
return Task.FromResult(default(PartitionProperties));
}
- public override EventHubProducer CreateProducer(EventHubProducerOptions producerOptions = default)
+ public override EventHubProducer CreateProducer(EventHubProducerOptions producerOptions,
+ EventHubRetryPolicy defaultRetry)
{
- CreateProducerCalledWithOptions = producerOptions;
+ CreateProducerCalledWith = (producerOptions, defaultRetry);
return default(EventHubProducer);
}
- public override EventHubConsumer CreateConsumer(string consumerGroup, string partitionId, EventPosition eventPosition, EventHubConsumerOptions consumerOptions)
+ public override EventHubConsumer CreateConsumer(string consumerGroup,
+ string partitionId,
+ EventPosition eventPosition,
+ EventHubConsumerOptions consumerOptions,
+ EventHubRetryPolicy defaultRetry)
{
- CreateConsumerCalledWith = (consumerGroup, partitionId, eventPosition, consumerOptions);
+ CreateConsumerCalledWith = (consumerGroup, partitionId, eventPosition, consumerOptions, defaultRetry);
return default(EventHubConsumer);
}
+ public override void UpdateRetryPolicy(EventHubRetryPolicy newRetryPolicy)
+ {
+ UpdateRetryPolicyCalledWith = newRetryPolicy;
+ }
+
public override Task CloseAsync(CancellationToken cancellationToken)
{
WasCloseCalled = true;
diff --git a/sdk/eventhub/Azure.Messaging.EventHubs/tests/EventHubConsumer/EventHubConsumerLiveTests.cs b/sdk/eventhub/Azure.Messaging.EventHubs/tests/EventHubConsumer/EventHubConsumerLiveTests.cs
index a7825b7873373..f814f1103ba3a 100755
--- a/sdk/eventhub/Azure.Messaging.EventHubs/tests/EventHubConsumer/EventHubConsumerLiveTests.cs
+++ b/sdk/eventhub/Azure.Messaging.EventHubs/tests/EventHubConsumer/EventHubConsumerLiveTests.cs
@@ -427,7 +427,7 @@ public async Task ReceiveCanReadLargeEvent()
new EventData(new byte[1000000])
};
- await using (var client = new EventHubClient(connectionString, new EventHubClientOptions { DefaultTimeout = TimeSpan.FromMinutes(2) }))
+ await using (var client = new EventHubClient(connectionString, new EventHubClientOptions { RetryOptions = new RetryOptions { TryTimeout = TimeSpan.FromMinutes(5) }}))
{
var partition = (await client.GetPartitionIdsAsync()).First();
diff --git a/sdk/eventhub/Azure.Messaging.EventHubs/tests/EventHubConsumer/EventHubConsumerOptionsTests.cs b/sdk/eventhub/Azure.Messaging.EventHubs/tests/EventHubConsumer/EventHubConsumerOptionsTests.cs
index aba67869bf16b..be017b36d68c1 100755
--- a/sdk/eventhub/Azure.Messaging.EventHubs/tests/EventHubConsumer/EventHubConsumerOptionsTests.cs
+++ b/sdk/eventhub/Azure.Messaging.EventHubs/tests/EventHubConsumer/EventHubConsumerOptionsTests.cs
@@ -9,7 +9,7 @@ namespace Azure.Messaging.EventHubs.Tests
///
///
[TestFixture]
- [Parallelizable(ParallelScope.Children)]
+ [Parallelizable(ParallelScope.All)]
public class EventHubConsumerOptionsTests
{
///
@@ -23,7 +23,7 @@ public void CloneProducesACopy()
var options = new EventHubConsumerOptions
{
OwnerLevel = 99,
- Retry = new ExponentialRetry(TimeSpan.FromSeconds(4), TimeSpan.FromSeconds(5), 6),
+ RetryOptions = new RetryOptions { Mode = RetryMode.Fixed },
DefaultMaximumReceiveWaitTime = TimeSpan.FromMinutes(65),
Identifier = "an_event_consumer"
};
@@ -35,8 +35,8 @@ public void CloneProducesACopy()
Assert.That(clone.DefaultMaximumReceiveWaitTime, Is.EqualTo(options.DefaultMaximumReceiveWaitTime), "The default maximum wait time of the clone should match.");
Assert.That(clone.Identifier, Is.EqualTo(options.Identifier), "The identifier of the clone should match.");
- Assert.That(ExponentialRetry.HaveSameConfiguration((ExponentialRetry)clone.Retry, (ExponentialRetry)options.Retry), "The retry of the clone should be considered equal.");
- Assert.That(clone.Retry, Is.Not.SameAs(options.Retry), "The retry of the clone should be a copy, not the same instance.");
+ Assert.That(clone.RetryOptions.IsEquivalentTo(options.RetryOptions), Is.True, "The retry options of the clone should be considered equal.");
+ Assert.That(clone.RetryOptions, Is.Not.SameAs(options.RetryOptions), "The retry options of the clone should be a copy, not the same instance.");
}
///
diff --git a/sdk/eventhub/Azure.Messaging.EventHubs/tests/EventHubConsumer/EventHubConsumerTests.cs b/sdk/eventhub/Azure.Messaging.EventHubs/tests/EventHubConsumer/EventHubConsumerTests.cs
index 6d808c9023331..786e40ee5a097 100755
--- a/sdk/eventhub/Azure.Messaging.EventHubs/tests/EventHubConsumer/EventHubConsumerTests.cs
+++ b/sdk/eventhub/Azure.Messaging.EventHubs/tests/EventHubConsumer/EventHubConsumerTests.cs
@@ -3,9 +3,11 @@
using System;
using System.Collections.Generic;
+using System.Reflection;
using System.Threading;
using System.Threading.Tasks;
using Azure.Messaging.EventHubs.Core;
+using Moq;
using NUnit.Framework;
namespace Azure.Messaging.EventHubs.Tests
@@ -16,7 +18,7 @@ namespace Azure.Messaging.EventHubs.Tests
///
///
[TestFixture]
- [Parallelizable(ParallelScope.Children)]
+ [Parallelizable(ParallelScope.All)]
public class EventHubConsumerTests
{
///
@@ -26,7 +28,7 @@ public class EventHubConsumerTests
[Test]
public void ConstructorValidatesTheConsumer()
{
- Assert.That(() => new EventHubConsumer(null, "dummy", EventHubConsumer.DefaultConsumerGroupName, "0", EventPosition.Latest, new EventHubConsumerOptions()), Throws.ArgumentNullException);
+ Assert.That(() => new EventHubConsumer(null, "dummy", EventHubConsumer.DefaultConsumerGroupName, "0", EventPosition.Latest, new EventHubConsumerOptions(), Mock.Of()), Throws.ArgumentNullException);
}
///
@@ -38,7 +40,7 @@ public void ConstructorValidatesTheConsumer()
[TestCase("")]
public void ConstructorValidatesTheEventHub(string eventHub)
{
- Assert.That(() => new EventHubConsumer(new ObservableTransportConsumerMock(), eventHub, EventHubConsumer.DefaultConsumerGroupName, "0", EventPosition.Earliest, new EventHubConsumerOptions()), Throws.InstanceOf());
+ Assert.That(() => new EventHubConsumer(new ObservableTransportConsumerMock(), eventHub, EventHubConsumer.DefaultConsumerGroupName, "0", EventPosition.Earliest, new EventHubConsumerOptions(), Mock.Of()), Throws.InstanceOf());
}
///
@@ -50,7 +52,7 @@ public void ConstructorValidatesTheEventHub(string eventHub)
[TestCase("")]
public void ConstructorValidatesThePartition(string partition)
{
- Assert.That(() => new EventHubConsumer(new ObservableTransportConsumerMock(), "dummy", EventHubConsumer.DefaultConsumerGroupName, partition, EventPosition.Earliest, new EventHubConsumerOptions()), Throws.InstanceOf());
+ Assert.That(() => new EventHubConsumer(new ObservableTransportConsumerMock(), "dummy", EventHubConsumer.DefaultConsumerGroupName, partition, EventPosition.Earliest, new EventHubConsumerOptions(), Mock.Of()), Throws.InstanceOf());
}
///
@@ -62,7 +64,7 @@ public void ConstructorValidatesThePartition(string partition)
[TestCase("")]
public void ConstructorValidatesTheConsumerGroup(string consumerGroup)
{
- Assert.That(() => new EventHubConsumer(new ObservableTransportConsumerMock(), "dummy", consumerGroup, "1332", EventPosition.Earliest, new EventHubConsumerOptions()), Throws.InstanceOf());
+ Assert.That(() => new EventHubConsumer(new ObservableTransportConsumerMock(), "dummy", consumerGroup, "1332", EventPosition.Earliest, new EventHubConsumerOptions(), Mock.Of()), Throws.InstanceOf());
}
///
@@ -72,7 +74,7 @@ public void ConstructorValidatesTheConsumerGroup(string consumerGroup)
[Test]
public void ConstructorValidatesTheEventPosition()
{
- Assert.That(() => new EventHubConsumer(new ObservableTransportConsumerMock(), "dummy", EventHubConsumer.DefaultConsumerGroupName, "1234", null, new EventHubConsumerOptions()), Throws.InstanceOf());
+ Assert.That(() => new EventHubConsumer(new ObservableTransportConsumerMock(), "dummy", EventHubConsumer.DefaultConsumerGroupName, "1234", null, new EventHubConsumerOptions(), Mock.Of()), Throws.InstanceOf());
}
///
@@ -82,7 +84,17 @@ public void ConstructorValidatesTheEventPosition()
[Test]
public void ConstructorValidatesTheOptions()
{
- Assert.That(() => new EventHubConsumer(new ObservableTransportConsumerMock(), "dummy", EventHubConsumer.DefaultConsumerGroupName, "0", EventPosition.Latest, null), Throws.ArgumentNullException);
+ Assert.That(() => new EventHubConsumer(new ObservableTransportConsumerMock(), "dummy", EventHubConsumer.DefaultConsumerGroupName, "0", EventPosition.Latest, null, Mock.Of()), Throws.ArgumentNullException);
+ }
+
+ ///
+ /// Verifies functionality of the constructor.
+ ///
+ ///
+ [Test]
+ public void ConstructorValidatesThDefaultRetryPoicy()
+ {
+ Assert.That(() => new EventHubConsumer(new ObservableTransportConsumerMock(), "dummy", EventHubConsumer.DefaultConsumerGroupName, "0", EventPosition.Latest, new EventHubConsumerOptions(), null), Throws.ArgumentNullException);
}
///
@@ -94,7 +106,7 @@ public void ConstructorSetsThePartition()
{
var partition = "aPartition";
var transportConsumer = new ObservableTransportConsumerMock();
- var consumer = new EventHubConsumer(transportConsumer, "dummy", EventHubConsumer.DefaultConsumerGroupName, partition, EventPosition.FromSequenceNumber(1), new EventHubConsumerOptions());
+ var consumer = new EventHubConsumer(transportConsumer, "dummy", EventHubConsumer.DefaultConsumerGroupName, partition, EventPosition.FromSequenceNumber(1), new EventHubConsumerOptions(), Mock.Of());
Assert.That(consumer.PartitionId, Is.EqualTo(partition));
}
@@ -115,7 +127,7 @@ public void ConstructorSetsThePriority(long? priority)
};
var transportConsumer = new ObservableTransportConsumerMock();
- var consumer = new EventHubConsumer(transportConsumer, "dummy", EventHubConsumer.DefaultConsumerGroupName, "0", EventPosition.FromOffset(65), options);
+ var consumer = new EventHubConsumer(transportConsumer, "dummy", EventHubConsumer.DefaultConsumerGroupName, "0", EventPosition.FromOffset(65), options, Mock.Of());
Assert.That(consumer.OwnerLevel, Is.EqualTo(priority));
}
@@ -129,7 +141,7 @@ public void ConstructorSetsTheStartingPosition()
{
var expectedPosition = EventPosition.FromSequenceNumber(5641);
var transportConsumer = new ObservableTransportConsumerMock();
- var consumer = new EventHubConsumer(transportConsumer, "dummy", EventHubConsumer.DefaultConsumerGroupName, "0", expectedPosition, new EventHubConsumerOptions());
+ var consumer = new EventHubConsumer(transportConsumer, "dummy", EventHubConsumer.DefaultConsumerGroupName, "0", expectedPosition, new EventHubConsumerOptions(), Mock.Of());
Assert.That(consumer.StartingPosition, Is.EqualTo(expectedPosition));
}
@@ -143,11 +155,76 @@ public void ConstructorSetsTheConsumerGroup()
{
var consumerGroup = "SomeGroup";
var transportConsumer = new ObservableTransportConsumerMock();
- var consumer = new EventHubConsumer(transportConsumer, "dummy", consumerGroup, "0", EventPosition.Latest, new EventHubConsumerOptions());
+ var consumer = new EventHubConsumer(transportConsumer, "dummy", consumerGroup, "0", EventPosition.Latest, new EventHubConsumerOptions(), Mock.Of());
Assert.That(consumer.ConsumerGroup, Is.EqualTo(consumerGroup));
}
+ ///
+ /// Verifies functionality of the constructor.
+ ///
+ ///
+ [Test]
+ public void ConstructorSetsTheRetryPolicy()
+ {
+ var retryPolicy = Mock.Of();
+ var transportConsumer = new ObservableTransportConsumerMock();
+ var consumer = new EventHubConsumer(transportConsumer, "dummy", "consumerGroup", "0", EventPosition.Latest, new EventHubConsumerOptions(), retryPolicy);
+
+ Assert.That(consumer.RetryPolicy, Is.SameAs(retryPolicy));
+ }
+
+ ///
+ /// Verifies functionality of the
+ /// setter.
+ ///
+ ///
+ [Test]
+ public void SettingTheRetryPolicyUpdatesState()
+ {
+ var retryOptions = new RetryOptions
+ {
+ Delay = TimeSpan.FromSeconds(1),
+ MaximumDelay = TimeSpan.FromSeconds(2),
+ TryTimeout = TimeSpan.FromSeconds(3),
+ MaximumRetries = 4,
+ Mode = RetryMode.Fixed
+ };
+
+ var customRetry = Mock.Of();
+ var consumerOptions = new EventHubConsumerOptions { RetryOptions = retryOptions };
+ var consumer = new EventHubConsumer(new ObservableTransportConsumerMock(), "dummy", "consumerGroup", "0", EventPosition.Latest, consumerOptions, new BasicRetryPolicy(retryOptions));
+
+ Assert.That(consumer.RetryPolicy, Is.InstanceOf(), "The retry policy should have been created from options");
+
+ consumer.RetryPolicy = customRetry;
+ Assert.That(consumer.RetryPolicy, Is.SameAs(customRetry), "The custom retry policy should have been set.");
+
+ var activeOptions = (EventHubConsumerOptions)
+ typeof(EventHubConsumer)
+ .GetProperty("Options", BindingFlags.Instance | BindingFlags.NonPublic)
+ .GetValue(consumer);
+
+ Assert.That(activeOptions.RetryOptions, Is.Null, "Setting a custom policy should clear the retry options.");
+ }
+
+ ///
+ /// Verifies functionality of the
+ /// setter.
+ ///
+ ///
+ [Test]
+ public void SettingTheRetryPolicyUpdatesTheTransportConsumer()
+ {
+ var customRetry = Mock.Of();
+ var transportConsumer = new ObservableTransportConsumerMock();
+ var consumerOptions = new EventHubConsumerOptions();
+ var consumer = new EventHubConsumer(transportConsumer, "dummy", "consumerGroup", "0", EventPosition.Latest, consumerOptions, Mock.Of());
+
+ consumer.RetryPolicy = customRetry;
+ Assert.That(transportConsumer.UpdateRetryPolicyCalledWith, Is.SameAs(customRetry), "The custom retry policy should have been set.");
+ }
+
///
/// Verifies functionality of the
/// method.
@@ -160,7 +237,7 @@ public void ConstructorSetsTheConsumerGroup()
public void ReceiveAsyncValidatesTheMaximumCount(int maximumMessageCount)
{
var transportConsumer = new ObservableTransportConsumerMock();
- var consumer = new EventHubConsumer(transportConsumer, "dummy", EventHubConsumer.DefaultConsumerGroupName, "0", EventPosition.Latest, new EventHubConsumerOptions());
+ var consumer = new EventHubConsumer(transportConsumer, "dummy", EventHubConsumer.DefaultConsumerGroupName, "0", EventPosition.Latest, new EventHubConsumerOptions(), Mock.Of());
var cancellation = new CancellationTokenSource();
var expectedWaitTime = TimeSpan.FromDays(1);
@@ -180,7 +257,7 @@ public void ReceiveAsyncValidatesTheMaximumCount(int maximumMessageCount)
public void ReceiveAsyncValidatesTheMaximumWaitTime(int timeSpanDelta)
{
var transportConsumer = new ObservableTransportConsumerMock();
- var consumer = new EventHubConsumer(transportConsumer, "dummy", EventHubConsumer.DefaultConsumerGroupName, "0", EventPosition.Latest, new EventHubConsumerOptions());
+ var consumer = new EventHubConsumer(transportConsumer, "dummy", EventHubConsumer.DefaultConsumerGroupName, "0", EventPosition.Latest, new EventHubConsumerOptions(), Mock.Of());
var cancellation = new CancellationTokenSource();
var expectedWaitTime = TimeSpan.FromMilliseconds(timeSpanDelta);
@@ -197,13 +274,13 @@ public async Task ReceiveAsyncInvokesTheTransportConsumer()
{
var options = new EventHubConsumerOptions { DefaultMaximumReceiveWaitTime = TimeSpan.FromMilliseconds(8) };
var transportConsumer = new ObservableTransportConsumerMock();
- var consumer = new EventHubConsumer(transportConsumer, "dummy", EventHubConsumer.DefaultConsumerGroupName, "0", EventPosition.Latest, options);
+ var consumer = new EventHubConsumer(transportConsumer, "dummy", EventHubConsumer.DefaultConsumerGroupName, "0", EventPosition.Latest, options, Mock.Of());
var cancellation = new CancellationTokenSource();
var expectedMessageCount = 45;
await consumer.ReceiveAsync(expectedMessageCount, null, cancellation.Token);
- (var actualMessageCount, var actualWaitTime) = transportConsumer.ReceiveCalledWithParameters;
+ (var actualMessageCount, var actualWaitTime) = transportConsumer.ReceiveCalledWith;
Assert.That(actualMessageCount, Is.EqualTo(expectedMessageCount), "The message counts should match.");
Assert.That(actualWaitTime, Is.EqualTo(options.DefaultMaximumReceiveWaitTime), "The wait time should match.");
@@ -218,7 +295,7 @@ public async Task ReceiveAsyncInvokesTheTransportConsumer()
public async Task CloseAsyncClosesTheTransportConsumer()
{
var transportConsumer = new ObservableTransportConsumerMock();
- var consumer = new EventHubConsumer(transportConsumer, "dummy", EventHubConsumer.DefaultConsumerGroupName, "0", EventPosition.Latest, new EventHubConsumerOptions());
+ var consumer = new EventHubConsumer(transportConsumer, "dummy", EventHubConsumer.DefaultConsumerGroupName, "0", EventPosition.Latest, new EventHubConsumerOptions(), Mock.Of());
await consumer.CloseAsync();
@@ -234,7 +311,7 @@ public async Task CloseAsyncClosesTheTransportConsumer()
public void CloseClosesTheTransportConsumer()
{
var transportConsumer = new ObservableTransportConsumerMock();
- var consumer = new EventHubConsumer(transportConsumer, "dummy", EventHubConsumer.DefaultConsumerGroupName, "0", EventPosition.Latest, new EventHubConsumerOptions());
+ var consumer = new EventHubConsumer(transportConsumer, "dummy", EventHubConsumer.DefaultConsumerGroupName, "0", EventPosition.Latest, new EventHubConsumerOptions(), Mock.Of());
consumer.Close();
@@ -248,16 +325,22 @@ public void CloseClosesTheTransportConsumer()
private class ObservableTransportConsumerMock : TransportEventHubConsumer
{
public bool WasCloseCalled = false;
- public (int, TimeSpan?) ReceiveCalledWithParameters;
+ public EventHubRetryPolicy UpdateRetryPolicyCalledWith;
+ public (int, TimeSpan?) ReceiveCalledWith;
public override Task> ReceiveAsync(int maximumMessageCount,
TimeSpan? maximumWaitTime,
CancellationToken cancellationToken)
{
- ReceiveCalledWithParameters = (maximumMessageCount, maximumWaitTime);
+ ReceiveCalledWith = (maximumMessageCount, maximumWaitTime);
return Task.FromResult(default(IEnumerable));
}
+ public override void UpdateRetryPolicy(EventHubRetryPolicy newRetryPolicy)
+ {
+ UpdateRetryPolicyCalledWith = newRetryPolicy;
+ }
+
public override Task CloseAsync(CancellationToken cancellationToken)
{
WasCloseCalled = true;
diff --git a/sdk/eventhub/Azure.Messaging.EventHubs/tests/EventHubProducer/EventHubProducerLiveTests.cs b/sdk/eventhub/Azure.Messaging.EventHubs/tests/EventHubProducer/EventHubProducerLiveTests.cs
index 3d80976268796..0986007845887 100755
--- a/sdk/eventhub/Azure.Messaging.EventHubs/tests/EventHubProducer/EventHubProducerLiveTests.cs
+++ b/sdk/eventhub/Azure.Messaging.EventHubs/tests/EventHubProducer/EventHubProducerLiveTests.cs
@@ -68,7 +68,7 @@ public async Task ProducerWithOptionsCanSend(TransportType transportType)
await using (var scope = await EventHubScope.CreateAsync(4))
{
var connectionString = TestEnvironment.BuildConnectionStringForEventHub(scope.EventHubName);
- var producerOptions = new EventHubProducerOptions { Retry = new ExponentialRetry(TimeSpan.FromSeconds(0.25), TimeSpan.FromSeconds(45), 5) };
+ var producerOptions = new EventHubProducerOptions { RetryOptions = new RetryOptions { MaximumRetries = 5 } };
await using (var client = new EventHubClient(connectionString, new EventHubClientOptions { TransportType = transportType }))
await using (var producer = client.CreateProducer(producerOptions))
@@ -229,7 +229,7 @@ public async Task ProducerCanSendSingleLargeEvent()
{
var connectionString = TestEnvironment.BuildConnectionStringForEventHub(scope.EventHubName);
- await using (var client = new EventHubClient(connectionString, new EventHubClientOptions { DefaultTimeout = TimeSpan.FromMinutes(2) }))
+ await using (var client = new EventHubClient(connectionString, new EventHubClientOptions { RetryOptions = new RetryOptions { TryTimeout = TimeSpan.FromMinutes(5) }}))
await using (var producer = client.CreateProducer())
{
// Actual limit is 1046520 for a single event.
@@ -333,7 +333,7 @@ public async Task ProducerCanSendLargeBatch()
{
var connectionString = TestEnvironment.BuildConnectionStringForEventHub(scope.EventHubName);
- await using (var client = new EventHubClient(connectionString, new EventHubClientOptions { DefaultTimeout = TimeSpan.FromMinutes(2) }))
+ await using (var client = new EventHubClient(connectionString, new EventHubClientOptions { RetryOptions = new RetryOptions { TryTimeout = TimeSpan.FromMinutes(5) }}))
await using (var producer = client.CreateProducer())
{
// Actual limit is 1046520 for a single event.
diff --git a/sdk/eventhub/Azure.Messaging.EventHubs/tests/EventHubProducer/EventHubProducerOptionsTests.cs b/sdk/eventhub/Azure.Messaging.EventHubs/tests/EventHubProducer/EventHubProducerOptionsTests.cs
index 3ea9125d5db66..44f794a0146ef 100755
--- a/sdk/eventhub/Azure.Messaging.EventHubs/tests/EventHubProducer/EventHubProducerOptionsTests.cs
+++ b/sdk/eventhub/Azure.Messaging.EventHubs/tests/EventHubProducer/EventHubProducerOptionsTests.cs
@@ -9,7 +9,7 @@ namespace Azure.Messaging.EventHubs.Tests
///
///
[TestFixture]
- [Parallelizable(ParallelScope.Children)]
+ [Parallelizable(ParallelScope.All)]
public class EventHubProducerOptionsTests
{
///
@@ -23,29 +23,15 @@ public void CloneProducesACopy()
var options = new EventHubProducerOptions
{
PartitionId = "some_partition_id_123",
- Retry = new ExponentialRetry(TimeSpan.FromSeconds(4), TimeSpan.FromSeconds(5), 6),
- Timeout = TimeSpan.FromMinutes(65)
+ RetryOptions = new RetryOptions { TryTimeout = TimeSpan.FromMinutes(36) }
};
var clone = options.Clone();
Assert.That(clone, Is.Not.Null, "The clone should not be null.");
Assert.That(clone.PartitionId, Is.EqualTo(options.PartitionId), "The partition identifier of the clone should match.");
- Assert.That(clone.Timeout, Is.EqualTo(options.Timeout), "The timeout of the clone should match.");
-
- Assert.That(ExponentialRetry.HaveSameConfiguration((ExponentialRetry)clone.Retry, (ExponentialRetry)options.Retry), "The retry of the clone should be considered equal.");
- Assert.That(clone.Retry, Is.Not.SameAs(options.Retry), "The retry of the clone should be a copy, not the same instance.");
- }
-
- ///
- /// Verifies functionality of the
- /// property.
- ///
- ///
- [Test]
- public void DefaultTimeoutIsValidated()
- {
- Assert.That(() => new EventHubProducerOptions { Timeout = TimeSpan.FromMilliseconds(-1) }, Throws.ArgumentException);
+ Assert.That(clone.RetryOptions.IsEquivalentTo(options.RetryOptions), Is.True, "The retry options of the clone should be considered equal.");
+ Assert.That(clone.RetryOptions, Is.Not.SameAs(options.RetryOptions), "The retry options of the clone should be a copy, not the same instance.");
}
///
@@ -72,23 +58,5 @@ public void PartitionIdAllowsNull()
{
Assert.That(() => new EventHubProducerOptions { PartitionId = null }, Throws.Nothing);
}
-
- ///
- /// Verifies functionality of the
- /// property.
- ///
- ///
- [Test]
- [TestCase(null)]
- [TestCase(0)]
- public void DefaultTimeoutUsesDefaultValueIfNormalizesValueINotSpecified(int? noTimeoutValue)
- {
- var options = new EventHubProducerOptions();
- var timeoutValue = (noTimeoutValue.HasValue) ? TimeSpan.Zero : (TimeSpan?)null;
-
- options.Timeout = timeoutValue;
- Assert.That(options.Timeout, Is.EqualTo(timeoutValue), "The value supplied by the caller should be preserved.");
- Assert.That(options.TimeoutOrDefault, Is.Null, "The timeout value should be normalized to null internally.");
- }
}
}
diff --git a/sdk/eventhub/Azure.Messaging.EventHubs/tests/EventHubProducer/EventHubProducerTests.cs b/sdk/eventhub/Azure.Messaging.EventHubs/tests/EventHubProducer/EventHubProducerTests.cs
index c74580e53f292..45728daed171b 100755
--- a/sdk/eventhub/Azure.Messaging.EventHubs/tests/EventHubProducer/EventHubProducerTests.cs
+++ b/sdk/eventhub/Azure.Messaging.EventHubs/tests/EventHubProducer/EventHubProducerTests.cs
@@ -4,6 +4,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
+using System.Reflection;
using System.Threading;
using System.Threading.Tasks;
using Azure.Messaging.EventHubs.Core;
@@ -18,7 +19,7 @@ namespace Azure.Messaging.EventHubs.Tests
///
///
[TestFixture]
- [Parallelizable(ParallelScope.Children)]
+ [Parallelizable(ParallelScope.All)]
public class EventHubProducerTests
{
///
@@ -28,7 +29,7 @@ public class EventHubProducerTests
[Test]
public void ConstructorValidatesTheProducer()
{
- Assert.That(() => new EventHubProducer(null, "dummy", new EventHubProducerOptions()), Throws.ArgumentNullException);
+ Assert.That(() => new EventHubProducer(null, "dummy", new EventHubProducerOptions(), Mock.Of()), Throws.ArgumentNullException);
}
///
@@ -40,7 +41,7 @@ public void ConstructorValidatesTheProducer()
[TestCase("")]
public void ConstructorValidatesTheEventHubPath(string eventHubPath)
{
- Assert.That(() => new EventHubProducer(new ObservableTransportProducerMock(), eventHubPath, new EventHubProducerOptions()), Throws.InstanceOf());
+ Assert.That(() => new EventHubProducer(new ObservableTransportProducerMock(), eventHubPath, new EventHubProducerOptions(), Mock.Of()), Throws.InstanceOf());
}
///
@@ -50,7 +51,17 @@ public void ConstructorValidatesTheEventHubPath(string eventHubPath)
[Test]
public void ConstructorValidatesTheOptions()
{
- Assert.That(() => new EventHubProducer(new ObservableTransportProducerMock(), "dummy", null), Throws.ArgumentNullException);
+ Assert.That(() => new EventHubProducer(new ObservableTransportProducerMock(), "dummy", null, Mock.Of()), Throws.ArgumentNullException);
+ }
+
+ ///
+ /// Verifies functionality of the constructor.
+ ///
+ ///
+ [Test]
+ public void ConstructorValidatesTheDefaultRetryPolicy()
+ {
+ Assert.That(() => new EventHubProducer(new ObservableTransportProducerMock(), "dummy", new EventHubProducerOptions(), null), Throws.InstanceOf());
}
///
@@ -63,10 +74,73 @@ public void ConstructorValidatesTheOptions()
[TestCase("someValue")]
public void ConstructorSetsTheEventHubPath(string eventHubPath)
{
- var producer = new EventHubProducer(new ObservableTransportProducerMock(), eventHubPath, new EventHubProducerOptions());
+ var producer = new EventHubProducer(new ObservableTransportProducerMock(), eventHubPath, new EventHubProducerOptions(), Mock.Of());
Assert.That(producer.EventHubPath, Is.EqualTo(eventHubPath));
}
+ ///
+ /// Verifies functionality of the constructor.
+ ///
+ ///
+ [Test]
+ public void ConstructorSetsTheRetryPolicy()
+ {
+ var retryPolicy = Mock.Of();
+ var producer = new EventHubProducer(new ObservableTransportProducerMock(), "path", new EventHubProducerOptions(), retryPolicy);
+ Assert.That(producer.RetryPolicy, Is.SameAs(retryPolicy));
+ }
+
+ ///
+ /// Verifies functionality of the
+ /// setter.
+ ///
+ ///
+ [Test]
+ public void SettingTheRetryUpdatesState()
+ {
+ var retryOptions = new RetryOptions
+ {
+ Delay = TimeSpan.FromSeconds(1),
+ MaximumDelay = TimeSpan.FromSeconds(2),
+ TryTimeout = TimeSpan.FromSeconds(3),
+ MaximumRetries = 4,
+ Mode = RetryMode.Fixed
+ };
+
+ var customRetry = Mock.Of();
+ var producerOptions = new EventHubProducerOptions { RetryOptions = retryOptions };
+ var producer = new EventHubProducer(new ObservableTransportProducerMock(), "dummy", producerOptions, new BasicRetryPolicy(retryOptions));
+
+ Assert.That(producer.RetryPolicy, Is.InstanceOf(), "The retry policy should have been created from options");
+
+ producer.RetryPolicy = customRetry;
+ Assert.That(producer.RetryPolicy, Is.SameAs(customRetry), "The custom retry policy should have been set.");
+
+ var activeOptions = (EventHubProducerOptions)
+ typeof(EventHubProducer)
+ .GetProperty("Options", BindingFlags.Instance | BindingFlags.NonPublic)
+ .GetValue(producer);
+
+ Assert.That(activeOptions.RetryOptions, Is.Null, "Setting a custom policy should clear the retry options.");
+ }
+
+ ///
+ /// Verifies functionality of the
+ /// setter.
+ ///
+ ///
+ [Test]
+ public void SettingTheRetryUpdatesTheTransportProducer()
+ {
+ var customRetry = Mock.Of();
+ var transportProducer = new ObservableTransportProducerMock();
+ var producerOptions = new EventHubProducerOptions();
+ var producer = new EventHubProducer(transportProducer, "dummy", producerOptions, Mock.Of());
+
+ producer.RetryPolicy = customRetry;
+ Assert.That(transportProducer.UpdateRetryPolicyCalledWith, Is.SameAs(customRetry), "The custom retry policy should have been passed to the transport producer.");
+ }
+
///
/// Verifies finctionality of the
///
@@ -75,7 +149,7 @@ public void ConstructorSetsTheEventHubPath(string eventHubPath)
public void SendSingleWithoutOptionsRequiresAnEvent()
{
var transportProducer = new ObservableTransportProducerMock();
- var producer = new EventHubProducer(transportProducer, "dummy", new EventHubProducerOptions());
+ var producer = new EventHubProducer(transportProducer, "dummy", new EventHubProducerOptions(), Mock.Of());
Assert.That(async () => await producer.SendAsync(default(EventData)), Throws.ArgumentNullException);
}
@@ -88,7 +162,7 @@ public void SendSingleWithoutOptionsRequiresAnEvent()
public void SendSingleRequiresAnEvent()
{
var transportProducer = new ObservableTransportProducerMock();
- var producer = new EventHubProducer(transportProducer, "dummy", new EventHubProducerOptions());
+ var producer = new EventHubProducer(transportProducer, "dummy", new EventHubProducerOptions(), Mock.Of());
Assert.That(async () => await producer.SendAsync(default(EventData), new SendOptions()), Throws.ArgumentNullException);
}
@@ -137,7 +211,7 @@ public async Task SendSingleWitOptionsDelegatesToBatchSend()
public void SendWithoutOptionsRequiresEvents()
{
var transportProducer = new ObservableTransportProducerMock();
- var producer = new EventHubProducer(transportProducer, "dummy", new EventHubProducerOptions());
+ var producer = new EventHubProducer(transportProducer, "dummy", new EventHubProducerOptions(), Mock.Of());
Assert.That(async () => await producer.SendAsync(default(IEnumerable)), Throws.ArgumentNullException);
}
@@ -150,7 +224,7 @@ public void SendWithoutOptionsRequiresEvents()
public void SendRequiresEvents()
{
var transportProducer = new ObservableTransportProducerMock();
- var producer = new EventHubProducer(transportProducer, "dummy", new EventHubProducerOptions());
+ var producer = new EventHubProducer(transportProducer, "dummy", new EventHubProducerOptions(), Mock.Of());
Assert.That(async () => await producer.SendAsync(default(IEnumerable), new SendOptions()), Throws.ArgumentNullException);
}
@@ -165,7 +239,7 @@ public void SendAllowsAPartitionHashKey()
var batchingOptions = new SendOptions { PartitionKey = "testKey" };
var events = new[] { new EventData(new byte[] { 0x44, 0x66, 0x88 }) };
var transportProducer = new ObservableTransportProducerMock();
- var producer = new EventHubProducer(transportProducer, "dummy", new EventHubProducerOptions());
+ var producer = new EventHubProducer(transportProducer, "dummy", new EventHubProducerOptions(), Mock.Of());
Assert.That(async () => await producer.SendAsync(events, batchingOptions), Throws.Nothing);
}
@@ -180,7 +254,7 @@ public void SendForASpecificPartitionDoesNotAllowAPartitionHashKey()
var batchingOptions = new SendOptions { PartitionKey = "testKey" };
var events = new[] { new EventData(new byte[] { 0x44, 0x66, 0x88 }) };
var transportProducer = new ObservableTransportProducerMock();
- var producer = new EventHubProducer(transportProducer, "dummy", new EventHubProducerOptions { PartitionId = "1" });
+ var producer = new EventHubProducer(transportProducer, "dummy", new EventHubProducerOptions { PartitionId = "1" }, Mock.Of());
Assert.That(async () => await producer.SendAsync(events, batchingOptions), Throws.InvalidOperationException);
}
@@ -194,11 +268,11 @@ public async Task SendWithoutOptionsInvokesTheTransportProducer()
{
var events = Mock.Of>();
var transportProducer = new ObservableTransportProducerMock();
- var producer = new EventHubProducer(transportProducer, "dummy", new EventHubProducerOptions());
+ var producer = new EventHubProducer(transportProducer, "dummy", new EventHubProducerOptions(), Mock.Of());
await producer.SendAsync(events);
- (var calledWithEvents, var calledWithOptions) = transportProducer.SendCalledWithParameters;
+ (var calledWithEvents, var calledWithOptions) = transportProducer.SendCalledWith;
Assert.That(calledWithEvents, Is.SameAs(events), "The events should be the same instance.");
Assert.That(calledWithOptions, Is.Not.Null, "A default set of options should be used.");
@@ -214,11 +288,11 @@ public async Task SendInvokesTheTransportProducer()
var events = Mock.Of>();
var options = new SendOptions();
var transportProducer = new ObservableTransportProducerMock();
- var producer = new EventHubProducer(transportProducer, "dummy", new EventHubProducerOptions());
+ var producer = new EventHubProducer(transportProducer, "dummy", new EventHubProducerOptions(), Mock.Of());
await producer.SendAsync(events, options);
- (var calledWithEvents, var calledWithOptions) = transportProducer.SendCalledWithParameters;
+ (var calledWithEvents, var calledWithOptions) = transportProducer.SendCalledWith;
Assert.That(calledWithEvents, Is.SameAs(events), "The events should be the same instance.");
Assert.That(calledWithOptions, Is.SameAs(options), "The options should be the same instance");
@@ -233,7 +307,7 @@ public async Task SendInvokesTheTransportProducer()
public async Task CloseAsyncClosesTheTransportProducer()
{
var transportProducer = new ObservableTransportProducerMock();
- var producer = new EventHubProducer(transportProducer, "dummy", new EventHubProducerOptions());
+ var producer = new EventHubProducer(transportProducer, "dummy", new EventHubProducerOptions(), Mock.Of());
await producer.CloseAsync();
@@ -249,7 +323,7 @@ public async Task CloseAsyncClosesTheTransportProducer()
public void CloseClosesTheTransportProducer()
{
var transportProducer = new ObservableTransportProducerMock();
- var producer = new EventHubProducer(transportProducer, "dummy", new EventHubProducerOptions());
+ var producer = new EventHubProducer(transportProducer, "dummy", new EventHubProducerOptions(), Mock.Of());
producer.Close();
@@ -263,16 +337,22 @@ public void CloseClosesTheTransportProducer()
private class ObservableTransportProducerMock : TransportEventHubProducer
{
public bool WasCloseCalled = false;
- public (IEnumerable, SendOptions) SendCalledWithParameters;
+ public EventHubRetryPolicy UpdateRetryPolicyCalledWith;
+ public (IEnumerable, SendOptions) SendCalledWith;
public override Task SendAsync(IEnumerable events,
SendOptions batchOptions,
CancellationToken cancellationToken)
{
- SendCalledWithParameters = (events, batchOptions);
+ SendCalledWith = (events, batchOptions);
return Task.CompletedTask;
}
+ public override void UpdateRetryPolicy(EventHubRetryPolicy newRetryPolicy)
+ {
+ UpdateRetryPolicyCalledWith = newRetryPolicy;
+ }
+
public override Task CloseAsync(CancellationToken cancellationToken)
{
WasCloseCalled = true;
diff --git a/sdk/eventhub/Azure.Messaging.EventHubs/tests/Infrastructure/EventDataExtensionTests.cs b/sdk/eventhub/Azure.Messaging.EventHubs/tests/Infrastructure/EventDataExtensionTests.cs
index cd0e4366b23e1..747499e885af5 100755
--- a/sdk/eventhub/Azure.Messaging.EventHubs/tests/Infrastructure/EventDataExtensionTests.cs
+++ b/sdk/eventhub/Azure.Messaging.EventHubs/tests/Infrastructure/EventDataExtensionTests.cs
@@ -11,7 +11,7 @@ namespace Azure.Messaging.EventHubs.Tests
///
///
[TestFixture]
- [Parallelizable(ParallelScope.Children)]
+ [Parallelizable(ParallelScope.All)]
public class EventDataExtensionsTests
{
///
diff --git a/sdk/eventhub/Azure.Messaging.EventHubs/tests/Infrastructure/RetryOptionsExtensions.cs b/sdk/eventhub/Azure.Messaging.EventHubs/tests/Infrastructure/RetryOptionsExtensions.cs
new file mode 100755
index 0000000000000..18c2e3762c5a8
--- /dev/null
+++ b/sdk/eventhub/Azure.Messaging.EventHubs/tests/Infrastructure/RetryOptionsExtensions.cs
@@ -0,0 +1,57 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+
+using System;
+
+namespace Azure.Messaging.EventHubs.Tests
+{
+ ///
+ /// The set of extension methods for the
+ /// class.
+ ///
+ ///
+ internal static class RetryOptionsExtensions
+ {
+ ///
+ /// Compares retry options between two instances to determine if the
+ /// instances represent the same set of options.
+ ///
+ ///
+ /// The instance that this method was invoked on.
+ /// The other set of retry options to consider.
+ ///
+ /// true, if the two sets of options are structurally equivilent; otherwise, false.
+ ///
+ public static bool IsEquivalentTo(this RetryOptions instance,
+ RetryOptions other)
+ {
+ // If the events are the same instance, they're equal. This should only happen
+ // if both are null or they are the exact same instance.
+
+ if (Object.ReferenceEquals(instance, other))
+ {
+ return true;
+ }
+
+ // If one or the other is null, then they cannot be equal, since we know that
+ // they are not both null.
+
+ if ((instance == null) || (other == null))
+ {
+ return false;
+ }
+
+ // If the contents of each attribute are equal, the instance are
+ // equal.
+
+ return
+ (
+ instance.Mode == other.Mode
+ && instance.MaximumRetries == other.MaximumRetries
+ && instance.Delay == other.Delay
+ && instance.MaximumDelay == other.MaximumDelay
+ && instance.TryTimeout == other.TryTimeout
+ );
+ }
+ }
+}
diff --git a/sdk/eventhub/Azure.Messaging.EventHubs/tests/Infrastructure/RetryOptionsExtensionsTests.cs b/sdk/eventhub/Azure.Messaging.EventHubs/tests/Infrastructure/RetryOptionsExtensionsTests.cs
new file mode 100755
index 0000000000000..7ef3541acef17
--- /dev/null
+++ b/sdk/eventhub/Azure.Messaging.EventHubs/tests/Infrastructure/RetryOptionsExtensionsTests.cs
@@ -0,0 +1,173 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+
+using System;
+using NUnit.Framework;
+
+namespace Azure.Messaging.EventHubs.Tests
+{
+ ///
+ /// The suite of tests for the
+ /// class.
+ ///
+ ///
+ [TestFixture]
+ [Parallelizable(ParallelScope.All)]
+ public class RetryOptionsExtensionsTests
+ {
+ ///
+ /// Verifies functionality of the test
+ /// helper.
+ ///
+ ///
+ [Test]
+ public void IsEquivalentToDetectsModes()
+ {
+ var first = new RetryOptions { Mode = RetryMode.Exponential };
+ var second = new RetryOptions { Mode = RetryMode.Fixed };
+
+ Assert.That(first.IsEquivalentTo(second), Is.False);
+ }
+
+ ///
+ /// Verifies functionality of the test
+ /// helper.
+ ///
+ ///
+ [Test]
+ public void IsEquivalentToDetectsMaximumRetries()
+ {
+ var first = new RetryOptions { MaximumRetries = 7 };
+ var second = new RetryOptions { MaximumRetries = 99 };
+
+ Assert.That(first.IsEquivalentTo(second), Is.False);
+ }
+
+ ///
+ /// Verifies functionality of the test
+ /// helper.
+ ///
+ ///
+ [Test]
+ public void IsEquivalentToDetectsMaximumDelay()
+ {
+ var first = new RetryOptions { MaximumDelay = TimeSpan.FromSeconds(7) };
+ var second = new RetryOptions { MaximumDelay = TimeSpan.FromSeconds(8) };
+
+ Assert.That(first.IsEquivalentTo(second), Is.False);
+ }
+
+ ///
+ /// Verifies functionality of the test
+ /// helper.
+ ///
+ ///
+ [Test]
+ public void IsEquivalentToDetectsDelay()
+ {
+ var first = new RetryOptions { Delay = TimeSpan.FromSeconds(7) };
+ var second = new RetryOptions { Delay = TimeSpan.FromMinutes(1) };
+
+ Assert.That(first.IsEquivalentTo(second), Is.False);
+ }
+
+ ///
+ /// Verifies functionality of the test
+ /// helper.
+ ///
+ ///
+ [Test]
+ public void IsEquivalentToDetectsTryTimeout()
+ {
+ var first = new RetryOptions { TryTimeout = TimeSpan.FromSeconds(9) };
+ var second = new RetryOptions { TryTimeout = TimeSpan.FromSeconds(8) };
+
+ Assert.That(first.IsEquivalentTo(second), Is.False);
+ }
+
+ ///
+ /// Verifies functionality of the test
+ /// helper.
+ ///
+ ///
+ [Test]
+ public void IsEquivalentToDetectsEqualOptionSets()
+ {
+ var first = new RetryOptions();
+ var second = new RetryOptions();
+
+ Assert.That(first.IsEquivalentTo(second), Is.True);
+ }
+
+ ///
+ /// Verifies functionality of the test
+ /// helper.
+ ///
+ ///
+ [Test]
+ public void IsEquivalentToDetectsSameInstance()
+ {
+ var first = new RetryOptions
+ {
+ Mode = RetryMode.Fixed,
+ MaximumRetries = 99,
+ MaximumDelay = TimeSpan.FromMinutes(3),
+ Delay = TimeSpan.FromSeconds(4),
+ TryTimeout = TimeSpan.Zero
+ };
+
+ Assert.That(first.IsEquivalentTo(first), Is.True);
+ }
+
+ ///
+ /// Verifies functionality of the test
+ /// helper.
+ ///
+ ///
+ [Test]
+ public void IsEquivalentToDetectsTwoNulls()
+ {
+ Assert.That(((RetryOptions)null).IsEquivalentTo(null), Is.True);
+ }
+
+ ///
+ /// Verifies functionality of the test
+ /// helper.
+ ///
+ ///
+ [Test]
+ public void IsEquivalentToDetectsNullInstance()
+ {
+ var first = new RetryOptions
+ {
+ Mode = RetryMode.Fixed,
+ MaximumRetries = 99,
+ MaximumDelay = TimeSpan.FromMinutes(3),
+ Delay = TimeSpan.FromSeconds(4),
+ TryTimeout = TimeSpan.Zero
+ };
+
+ Assert.That(((RetryOptions)null).IsEquivalentTo(first), Is.False);
+ }
+
+ ///
+ /// Verifies functionality of the test
+ /// helper.
+ ///
+ ///
+ [Test]
+ public void IsEquivalentToDetectsNullArgument()
+ {
+ var first = new RetryOptions
+ {
+ Mode = RetryMode.Fixed,
+ MaximumRetries = 99,
+ MaximumDelay = TimeSpan.FromMinutes(3),
+ Delay = TimeSpan.FromSeconds(4),
+ TryTimeout = TimeSpan.Zero
+ };
+
+ Assert.That(first.IsEquivalentTo((RetryOptions)null), Is.False);
+ }
+ }
+}
diff --git a/sdk/eventhub/Azure.Messaging.EventHubs/tests/RetryPolicies/BasicRetryPolicyTests.cs b/sdk/eventhub/Azure.Messaging.EventHubs/tests/RetryPolicies/BasicRetryPolicyTests.cs
new file mode 100755
index 0000000000000..06a626ddc0cf0
--- /dev/null
+++ b/sdk/eventhub/Azure.Messaging.EventHubs/tests/RetryPolicies/BasicRetryPolicyTests.cs
@@ -0,0 +1,332 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+
+using System;
+using System.Collections.Generic;
+using System.Net.Sockets;
+using System.Threading;
+using System.Threading.Tasks;
+using Azure.Messaging.EventHubs.Core;
+using Azure.Messaging.EventHubs.Errors;
+using Moq;
+using NUnit.Framework;
+
+namespace Azure.Messaging.EventHubs.Tests
+{
+ ///
+ /// The suite of tests for the
+ /// class.
+ ///
+ ///
+ [TestFixture]
+ [Parallelizable(ParallelScope.All)]
+ public class BasicRetryPolicyTests
+ {
+ ///
+ /// The test cases for exception types known to be retriable.
+ ///
+ ///
+ public static IEnumerable RetriableExceptionTestCases()
+ {
+ yield return new object[] { new TimeoutException() };
+ yield return new object[] { new OperationCanceledException() };
+ yield return new object[] { new SocketException(500) };
+
+ // Task Canceled should use the inner exception as the decision point.
+
+ yield return new object[] { new TaskCanceledException("dummy", new EventHubsException(true, null)) };
+
+ // Aggregate should use the first inner exception as the decision point.
+
+ yield return new object[]
+ {
+ new AggregateException(new Exception[]
+ {
+ new EventHubsException(true, null),
+ new ArgumentException()
+ })
+ };
+ }
+
+ ///
+ /// The test cases for exception types known to be non-retriable.
+ ///
+ ///
+ public static IEnumerable NonRetriableExceptionTestCases()
+ {
+ yield return new object[] { new ArgumentException() };
+ yield return new object[] { new InvalidOperationException() };
+ yield return new object[] { new NotSupportedException() };
+ yield return new object[] { new NullReferenceException() };
+ yield return new object[] { new OutOfMemoryException() };
+ yield return new object[] { new ObjectDisposedException("dummy") };
+
+ // Task Canceled should use the inner exception as the decision point.
+
+ yield return new object[] { new TaskCanceledException("dummy", new EventHubsException(false, null)) };
+
+ // Null is not retriable, even if it is a blessed type.
+
+ yield return new object[] { (TimeoutException)null };
+
+ // Aggregate should use the first inner exception as the decision point.
+
+ yield return new object[]
+ {
+ new AggregateException(new Exception[]
+ {
+ new ArgumentException(),
+ new EventHubsException(true, null),
+ new TimeoutException()
+ })
+ };
+ }
+
+ ///
+ /// Verifies functionality of the
+ /// method.
+ ///
+ ///
+ [Test]
+ [TestCase(0)]
+ [TestCase(1)]
+ [TestCase(2)]
+ [TestCase(10)]
+ [TestCase(100)]
+ public void CalulateTryTimeoutRespectsOptions(int attemptCount)
+ {
+ var timeout = TimeSpan.FromSeconds(5);
+ var options = new RetryOptions { TryTimeout = timeout };
+ var policy = new BasicRetryPolicy(options);
+
+ Assert.That(policy.CalculateTryTimeout(attemptCount), Is.EqualTo(options.TryTimeout));
+ }
+
+ ///
+ /// Verifies functionality of the
+ /// method.
+ ///
+ ///
+ [Test]
+ public void CalculateRetryDelayDoesNotRetryWhenThereIsNoMaximumRetries()
+ {
+ var policy = new BasicRetryPolicy(new RetryOptions
+ {
+ MaximumRetries = 0,
+ Delay = TimeSpan.FromSeconds(1),
+ MaximumDelay = TimeSpan.FromHours(1),
+ Mode = RetryMode.Fixed
+ });
+
+ Assert.That(policy.CalculateRetryDelay(Mock.Of(), -1), Is.Null);
+ }
+
+ ///
+ /// Verifies functionality of the
+ /// method.
+ ///
+ ///
+ [Test]
+ public void CalculateRetryDelayDoesNotRetryWhenThereIsNoMaximumDelay()
+ {
+ var policy = new BasicRetryPolicy(new RetryOptions
+ {
+ MaximumRetries = 99,
+ Delay = TimeSpan.FromSeconds(1),
+ MaximumDelay = TimeSpan.Zero,
+ Mode = RetryMode.Fixed
+ });
+
+ Assert.That(policy.CalculateRetryDelay(Mock.Of(), 88), Is.Null);
+ }
+
+ ///
+ /// Verifies functionality of the
+ /// method.
+ ///
+ ///
+ [Test]
+ public void CalculateRetryDelayDoesNotRetryWhenThereIsNoDelay()
+ {
+ var policy = new BasicRetryPolicy(new RetryOptions
+ {
+ MaximumRetries = 99,
+ Delay = TimeSpan.Zero,
+ MaximumDelay = TimeSpan.FromSeconds(1),
+ Mode = RetryMode.Fixed
+ });
+
+ Assert.That(policy.CalculateRetryDelay(Mock.Of(), 88), Is.Null);
+ }
+
+ ///
+ /// Verifies functionality of the
+ /// method.
+ ///
+ ///
+ [Test]
+ [TestCase(6)]
+ [TestCase(9)]
+ [TestCase(14)]
+ [TestCase(200)]
+ public void CalculateRetryDelayDoesNotRetryWhenAttemptsExceedTheMaximum(int retryAttempt)
+ {
+ var policy = new BasicRetryPolicy(new RetryOptions
+ {
+ MaximumRetries = 5,
+ Delay = TimeSpan.FromSeconds(1),
+ MaximumDelay = TimeSpan.FromHours(1),
+ Mode = RetryMode.Fixed
+ });
+
+ Assert.That(policy.CalculateRetryDelay(Mock.Of(), retryAttempt), Is.Null);
+ }
+
+ ///
+ /// Verifies functionality of the
+ /// method.
+ ///
+ ///
+ [Test]
+ public void CalculateRetryDelayAllowsRetryForTransientExceptions()
+ {
+ var policy = new BasicRetryPolicy(new RetryOptions
+ {
+ MaximumRetries = 99,
+ Delay = TimeSpan.FromSeconds(1),
+ MaximumDelay = TimeSpan.FromSeconds(100),
+ Mode = RetryMode.Fixed
+ });
+
+ Assert.That(policy.CalculateRetryDelay(new EventHubsException(true, null, null), 88), Is.Not.Null);
+ }
+
+ ///
+ /// Verifies functionality of the
+ /// method.
+ ///
+ ///
+ [Test]
+ [TestCaseSource(nameof(RetriableExceptionTestCases))]
+ public void CalculateRetryDelayAllowsRetryForKnownRetriableExceptions(Exception exception)
+ {
+ var policy = new BasicRetryPolicy(new RetryOptions
+ {
+ MaximumRetries = 99,
+ Delay = TimeSpan.FromSeconds(1),
+ MaximumDelay = TimeSpan.FromSeconds(100),
+ Mode = RetryMode.Fixed
+ });
+
+ Assert.That(policy.CalculateRetryDelay(exception, 88), Is.Not.Null);
+ }
+
+ ///
+ /// Verifies functionality of the
+ /// method.
+ ///
+ ///
+ [Test]
+ [TestCaseSource(nameof(NonRetriableExceptionTestCases))]
+ public void CalculateRetryDelayDoesNotRetryForNotKnownRetriableExceptions(Exception exception)
+ {
+ var policy = new BasicRetryPolicy(new RetryOptions
+ {
+ MaximumRetries = 99,
+ Delay = TimeSpan.FromSeconds(1),
+ MaximumDelay = TimeSpan.FromSeconds(100),
+ Mode = RetryMode.Fixed
+ });
+
+ Assert.That(policy.CalculateRetryDelay(exception, 88), Is.Null);
+ }
+
+ ///
+ /// Verifies functionality of the
+ /// method.
+ ///
+ ///
+ [Test]
+ [TestCase(1)]
+ [TestCase(2)]
+ [TestCase(30)]
+ [TestCase(60)]
+ [TestCase(240)]
+ public void CalculateRetryDelayRespectsMaximumDuration(int delaySeconds)
+ {
+ var policy = new BasicRetryPolicy(new RetryOptions
+ {
+ MaximumRetries = 99,
+ Delay = TimeSpan.FromSeconds(delaySeconds),
+ MaximumDelay = TimeSpan.FromSeconds(1),
+ Mode = RetryMode.Fixed
+ });
+
+ Assert.That(policy.CalculateRetryDelay(Mock.Of(), 88), Is.EqualTo(policy.Options.MaximumDelay));
+ }
+
+ ///
+ /// Verifies functionality of the
+ /// method.
+ ///
+ ///
+ [Test]
+ [TestCase(1)]
+ [TestCase(2)]
+ [TestCase(30)]
+ [TestCase(60)]
+ [TestCase(120)]
+ public void CalculateRetryDelayUsesFixedMode(int iterations)
+ {
+ var policy = new BasicRetryPolicy(new RetryOptions
+ {
+ MaximumRetries = 99,
+ Delay = TimeSpan.FromSeconds(iterations),
+ MaximumDelay = TimeSpan.FromHours(72),
+ Mode = RetryMode.Fixed
+ });
+
+ var variance = TimeSpan.FromSeconds(policy.Options.Delay.TotalSeconds * policy.JitterFactor);
+
+ for (var index = 0; index < iterations; ++index)
+ {
+ Assert.That(policy.CalculateRetryDelay(Mock.Of(), 88), Is.EqualTo(policy.Options.Delay).Within(variance), $"Iteration: { index } produced an unexpected delay.");
+ }
+ }
+
+ ///
+ /// Verifies functionality of the
+ /// method.
+ ///
+ ///
+ [Test]
+ [TestCase(1)]
+ [TestCase(2)]
+ [TestCase(5)]
+ [TestCase(10)]
+ [TestCase(25)]
+ public void CalculateRetryDelayUsesExponentialMode(int iterations)
+ {
+ var policy = new BasicRetryPolicy(new RetryOptions
+ {
+ MaximumRetries = 99,
+ Delay = TimeSpan.FromMilliseconds(15),
+ MaximumDelay = TimeSpan.FromHours(50000),
+ Mode = RetryMode.Exponential
+ });
+
+ var previousDelay = TimeSpan.Zero;
+
+ for (var index = 0; index < iterations; ++index)
+ {
+ var variance = TimeSpan.FromSeconds((policy.Options.Delay.TotalSeconds * index) * policy.JitterFactor);
+ var delay = policy.CalculateRetryDelay(Mock.Of(), index);
+
+ Assert.That(delay.HasValue, Is.True, $"Iteration: { index } did not have a value.");
+ Assert.That(delay.Value, Is.GreaterThan(previousDelay.Add(variance)), $"Iteration: { index } produced an unexpected delay.");
+
+ previousDelay = delay.Value;
+ }
+ }
+ }
+}
diff --git a/sdk/eventhub/Azure.Messaging.EventHubs/tests/RetryPolicies/RetryOptionsTests.cs b/sdk/eventhub/Azure.Messaging.EventHubs/tests/RetryPolicies/RetryOptionsTests.cs
new file mode 100755
index 0000000000000..e454d7824c307
--- /dev/null
+++ b/sdk/eventhub/Azure.Messaging.EventHubs/tests/RetryPolicies/RetryOptionsTests.cs
@@ -0,0 +1,114 @@
+using System;
+using NUnit.Framework;
+
+namespace Azure.Messaging.EventHubs.Tests
+{
+ ///
+ /// The suite of tests for the
+ /// class.
+ ///
+ ///
+ [TestFixture]
+ [Parallelizable(ParallelScope.All)]
+ public class RetryOptionsTests
+ {
+ ///
+ /// Verifies functionality of the
+ /// method.
+ ///
+ ///
+ [Test]
+ public void CloneProducesACopy()
+ {
+ var options = new RetryOptions
+ {
+ Mode = RetryMode.Fixed,
+ MaximumRetries = 65,
+ Delay = TimeSpan.FromSeconds(1),
+ MaximumDelay = TimeSpan.FromSeconds(2),
+ TryTimeout = TimeSpan.FromSeconds(3)
+ };
+
+ var clone = options.Clone();
+ Assert.That(clone, Is.Not.Null, "The clone should not be null.");
+
+ Assert.That(clone.Mode, Is.EqualTo(options.Mode), "The mode of the clone should match.");
+ Assert.That(clone.MaximumRetries, Is.EqualTo(options.MaximumRetries), "The maximum retry limit of the clone should match.");
+ Assert.That(clone.Delay, Is.EqualTo(options.Delay), "The delay of the clone should match.");
+ Assert.That(clone.MaximumDelay, Is.EqualTo(options.MaximumDelay), "The maximum delay of the clone should match.");
+ Assert.That(clone.TryTimeout, Is.EqualTo(options.TryTimeout), "The per-try of the clone should match.");
+ }
+
+ ///
+ /// Verifies that setting the is
+ /// validated.
+ ///
+ ///
+ [Test]
+ [TestCase(-1)]
+ [TestCase(-2)]
+ [TestCase(-9999)]
+ [TestCase(101)]
+ [TestCase(106)]
+ [TestCase(1000)]
+ public void MaximumRetriesIsValidated(int invalidValue)
+ {
+ var options = new RetryOptions();
+ Assert.That(() => options.MaximumRetries = invalidValue, Throws.InstanceOf());
+ }
+
+ ///
+ /// Verifies that setting the is
+ /// validated.
+ ///
+ ///
+ [Test]
+ [TestCase(-1)]
+ [TestCase(-10)]
+ [TestCase(-9999)]
+ [TestCase(301)]
+ [TestCase(306)]
+ [TestCase(500)]
+ public void DelayIsValidated(int seconds)
+ {
+ var options = new RetryOptions();
+ var invalidValue = TimeSpan.FromSeconds(seconds);
+ Assert.That(() => options.Delay = invalidValue, Throws.InstanceOf());
+ }
+
+ ///
+ /// Verifies that setting the is
+ /// validated.
+ ///
+ ///
+ [Test]
+ [TestCase(-1)]
+ [TestCase(-10)]
+ [TestCase(-9999)]
+ public void MaximumDelayIsValidated(int seconds)
+ {
+ var options = new RetryOptions();
+ var invalidValue = TimeSpan.FromSeconds(seconds);
+ Assert.That(() => options.MaximumDelay = invalidValue, Throws.InstanceOf());
+ }
+
+ ///
+ /// Verifies that setting the is
+ /// validated.
+ ///
+ ///
+ [Test]
+ [TestCase(-1)]
+ [TestCase(-10)]
+ [TestCase(-9999)]
+ [TestCase(3601)]
+ [TestCase(3605)]
+ [TestCase(5000)]
+ public void TryTimeoutIsValidated(int seconds)
+ {
+ var options = new RetryOptions();
+ var invalidValue = TimeSpan.FromSeconds(seconds);
+ Assert.That(() => options.TryTimeout = invalidValue, Throws.InstanceOf());
+ }
+ }
+}