public async Task ExpireClosesTheRemovedItemWhenForced() { var transportProducer = new ObservableTransportProducerMock(); var startingPool = new ConcurrentDictionary <string, TransportProducerPool.PoolItem> { ["0"] = new TransportProducerPool.PoolItem("0", transportProducer), ["1"] = new TransportProducerPool.PoolItem("1", transportProducer), ["2"] = new TransportProducerPool.PoolItem("2", transportProducer), }; var transportProducerPool = new TransportProducerPool(partition => transportProducer, pool: startingPool, eventHubProducer: transportProducer); // Validate the initial state. Assert.That(startingPool.TryGetValue("0", out _), Is.True, "The requested partition should appear in the pool."); Assert.That(transportProducer.CloseCallCount, Is.EqualTo(0), "The producer should not have been closed."); // Request the producer and hold the reference to ensure that it is flagged as being in use. await using var poolItem = transportProducerPool.GetPooledProducer("0"); // Expire the producer and validate the removal state. await transportProducerPool.ExpirePooledProducerAsync("0", forceClose : true); Assert.That(startingPool.TryGetValue("0", out _), Is.False, "The requested partition should have been removed."); Assert.That(transportProducer.CloseCallCount, Is.EqualTo(1), "The producer should have been closed."); }
/// <summary> /// Initializes a new instance of the <see cref="EventHubProducerClient" /> class. /// </summary> /// /// <param name="connection">The <see cref="EventHubConnection" /> connection to use for communication with the Event Hubs service.</param> /// <param name="clientOptions">A set of options to apply when configuring the producer.</param> /// public EventHubProducerClient(EventHubConnection connection, EventHubProducerClientOptions clientOptions = default) { Argument.AssertNotNull(connection, nameof(connection)); clientOptions = clientOptions?.Clone() ?? new EventHubProducerClientOptions(); OwnsConnection = false; Connection = connection; RetryPolicy = clientOptions.RetryOptions.ToRetryPolicy(); PartitionProducerPool = new TransportProducerPool(Connection, RetryPolicy); }
/// <summary> /// Initializes a new instance of the <see cref="EventHubProducerClient" /> class. /// </summary> /// /// <param name="connection">The connection to use as the basis for delegation of client-type operations.</param> /// <param name="transportProducer">The transport producer instance to use as the basis for service communication.</param> /// <param name="partitionProducerPool">A <see cref="TransportProducerPool" /> used to manage a set of partition specific <see cref="TransportProducer" />.</param> /// /// <remarks> /// This constructor is intended to be used internally for functional /// testing only. /// </remarks> /// internal EventHubProducerClient(EventHubConnection connection, TransportProducer transportProducer, TransportProducerPool partitionProducerPool = default) { Argument.AssertNotNull(connection, nameof(connection)); Argument.AssertNotNull(transportProducer, nameof(transportProducer)); OwnsConnection = false; Connection = connection; RetryPolicy = new EventHubsRetryOptions().ToRetryPolicy(); PartitionProducerPool = partitionProducerPool ?? new TransportProducerPool(Connection, RetryPolicy, eventHubProducer: transportProducer); }
/// <summary> /// Initializes a new instance of the <see cref="EventHubProducerClient" /> class. /// </summary> /// /// <param name="connectionString">The connection string to use for connecting to the Event Hubs namespace; it is expected that the shared key properties are contained in this connection string, but not the Event Hub name.</param> /// <param name="eventHubName">The name of the specific Event Hub to associate the producer with.</param> /// <param name="clientOptions">A set of options to apply when configuring the producer.</param> /// /// <remarks> /// If the connection string is copied from the Event Hub itself, it will contain the name of the desired Event Hub, /// and can be used directly without passing the <paramref name="eventHubName" />. The name of the Event Hub should be /// passed only once, either as part of the connection string or separately. /// </remarks> /// /// <seealso href="https://docs.microsoft.com/en-us/azure/event-hubs/event-hubs-get-connection-string"/> /// public EventHubProducerClient(string connectionString, string eventHubName, EventHubProducerClientOptions clientOptions) { Argument.AssertNotNullOrEmpty(connectionString, nameof(connectionString)); clientOptions = clientOptions?.Clone() ?? new EventHubProducerClientOptions(); OwnsConnection = true; Connection = new EventHubConnection(connectionString, eventHubName, clientOptions.ConnectionOptions); RetryPolicy = clientOptions.RetryOptions.ToRetryPolicy(); PartitionProducerPool = new TransportProducerPool(Connection, RetryPolicy); }
public void TransportProducerPoolAllowsTakingTheRightTransportProducer(string partitionId) { var transportProducer = new ObservableTransportProducerMock(); var partitionProducer = new ObservableTransportProducerMock(partitionId); var connection = new MockConnection(() => partitionProducer); var retryPolicy = new EventHubProducerClientOptions().RetryOptions.ToRetryPolicy(); var startingPool = new ConcurrentDictionary <string, TransportProducerPool.PoolItem> { ["0"] = new TransportProducerPool.PoolItem("0", partitionProducer) }; TransportProducerPool transportProducerPool = new TransportProducerPool(connection, retryPolicy, eventHubProducer: transportProducer); var returnedProducer = transportProducerPool.GetPooledProducer(partitionId).TransportProducer as ObservableTransportProducerMock; Assert.That(returnedProducer.PartitionId, Is.EqualTo(partitionId)); }
/// <summary> /// Initializes a new instance of the <see cref="EventHubProducerClient" /> class. /// </summary> /// /// <param name="fullyQualifiedNamespace">The fully qualified Event Hubs namespace to connect to. This is likely to be similar to <c>{yournamespace}.servicebus.windows.net</c>.</param> /// <param name="eventHubName">The name of the specific Event Hub to associate the producer with.</param> /// <param name="credential">The Azure managed identity credential to use for authorization. Access controls may be specified by the Event Hubs namespace or the requested Event Hub, depending on Azure configuration.</param> /// <param name="clientOptions">A set of options to apply when configuring the producer.</param> /// public EventHubProducerClient(string fullyQualifiedNamespace, string eventHubName, TokenCredential credential, EventHubProducerClientOptions clientOptions = default) { Argument.AssertWellFormedEventHubsNamespace(fullyQualifiedNamespace, nameof(fullyQualifiedNamespace)); Argument.AssertNotNullOrEmpty(eventHubName, nameof(eventHubName)); Argument.AssertNotNull(credential, nameof(credential)); clientOptions = clientOptions?.Clone() ?? new EventHubProducerClientOptions(); OwnsConnection = true; Connection = new EventHubConnection(fullyQualifiedNamespace, eventHubName, credential, clientOptions.ConnectionOptions); RetryPolicy = clientOptions.RetryOptions.ToRetryPolicy(); PartitionProducerPool = new TransportProducerPool(Connection, RetryPolicy); }
public async Task PoolItemsAreRefreshedOnDisposal() { var transportProducer = new ObservableTransportProducerMock(); var startingPool = new ConcurrentDictionary <string, TransportProducerPool.PoolItem> { ["0"] = new TransportProducerPool.PoolItem("0", transportProducer) }; var connection = new MockConnection(() => transportProducer); var retryPolicy = new EventHubProducerClientOptions().RetryOptions.ToRetryPolicy(); TransportProducerPool transportProducerPool = new TransportProducerPool(connection, retryPolicy); var expectedTime = DateTimeOffset.UtcNow.AddMinutes(10); await using var pooledProducer = transportProducerPool.GetPooledProducer("0"); // This call should refresh the timespan associated to an item in the pool await pooledProducer.DisposeAsync(); Assert.That(startingPool["0"].RemoveAfter, Is.InRange(expectedTime.AddMinutes(-1), expectedTime.AddMinutes(1)), $"The remove after of a pool item should be extended."); }
public void CloseAsyncSurfacesExceptionsForPartitionTransportProducer() { var transportProducer = new Mock <TransportProducer>(); var partitionProducer = new Mock <TransportProducer>(); var connection = new MockConnection(() => partitionProducer.Object); var retryPolicy = new EventHubProducerClientOptions().RetryOptions.ToRetryPolicy(); var startingPool = new ConcurrentDictionary <string, TransportProducerPool.PoolItem> { ["0"] = new TransportProducerPool.PoolItem("0", partitionProducer.Object) }; TransportProducerPool transportProducerPool = new TransportProducerPool(connection, retryPolicy, eventHubProducer: transportProducer.Object); partitionProducer .Setup(producer => producer.CloseAsync(It.IsAny <CancellationToken>())) .Returns(Task.FromException(new InvalidCastException())); var _ = transportProducerPool.GetPooledProducer("0"); Assert.That(async() => await transportProducerPool.CloseAsync(), Throws.InstanceOf <InvalidCastException>()); }
public void TransportProducerPoolRefreshesAccessedItems() { DateTimeOffset oneMinuteAgo = DateTimeOffset.UtcNow.Subtract(TimeSpan.FromMinutes(1)); var transportProducer = new ObservableTransportProducerMock(); var connection = new MockConnection(() => transportProducer); var retryPolicy = new EventHubProducerClientOptions().RetryOptions.ToRetryPolicy(); var startingPool = new ConcurrentDictionary <string, TransportProducerPool.PoolItem> { // An expired item in the pool ["0"] = new TransportProducerPool.PoolItem("0", transportProducer, removeAfter: oneMinuteAgo) }; TransportProducerPool transportProducerPool = new TransportProducerPool(connection, retryPolicy, startingPool); // This call should refresh the timespan associated to the item _ = transportProducerPool.GetPooledProducer("0"); // The expiration call back should not remove the item GetExpirationCallBack(transportProducerPool).Invoke(null); Assert.That(startingPool.TryGetValue("0", out _), Is.True, "The item in the pool should be refreshed and not have been removed."); }
public async Task TransportProducerPoolAllowsConfiguringRemoveAfter() { var transportProducer = new ObservableTransportProducerMock(); var connection = new MockConnection(() => transportProducer); var retryPolicy = new EventHubProducerClientOptions().RetryOptions.ToRetryPolicy(); var startingPool = new ConcurrentDictionary <string, TransportProducerPool.PoolItem> { ["0"] = new TransportProducerPool.PoolItem("0", transportProducer) }; TransportProducerPool transportProducerPool = new TransportProducerPool(connection, retryPolicy, startingPool); var pooledProducer = transportProducerPool.GetPooledProducer("0", TimeSpan.FromMinutes(-1)); await using (var _ = pooledProducer.ConfigureAwait(false)) { }; GetExpirationCallBack(transportProducerPool).Invoke(null); Assert.That(transportProducer.CloseCallCount, Is.EqualTo(1)); }
public void TransportProducerPoolRemovesExpiredItems() { var transportProducer = new ObservableTransportProducerMock(); var connection = new MockConnection(() => transportProducer); var retryPolicy = new EventHubProducerClientOptions().RetryOptions.ToRetryPolicy(); DateTimeOffset oneMinuteAgo = DateTimeOffset.UtcNow.Subtract(TimeSpan.FromMinutes(1)); var startingPool = new ConcurrentDictionary <string, TransportProducerPool.PoolItem> { // An expired item in the pool ["0"] = new TransportProducerPool.PoolItem("0", transportProducer, removeAfter: oneMinuteAgo), ["1"] = new TransportProducerPool.PoolItem("0", transportProducer), ["2"] = new TransportProducerPool.PoolItem("0", transportProducer), }; TransportProducerPool transportProducerPool = new TransportProducerPool(connection, retryPolicy, startingPool); GetExpirationCallBack(transportProducerPool).Invoke(null); Assert.That(startingPool.TryGetValue("0", out _), Is.False, "PerformExpiration should remove an expired producer from the pool."); Assert.That(transportProducer.CloseCallCount, Is.EqualTo(1), "PerformExpiration should close an expired producer."); Assert.That(startingPool.TryGetValue("1", out _), Is.True, "PerformExpiration should not remove valid producers."); Assert.That(startingPool.TryGetValue("2", out _), Is.True, "PerformExpiration should not remove valid producers."); }
public async Task ExpireRemovesTheRequestedItem() { var wasFactoryCalled = false; var transportProducer = new ObservableTransportProducerMock(); var startingPool = new ConcurrentDictionary <string, TransportProducerPool.PoolItem> { ["0"] = new TransportProducerPool.PoolItem("0", transportProducer), ["1"] = new TransportProducerPool.PoolItem("1", transportProducer), ["2"] = new TransportProducerPool.PoolItem("2", transportProducer), }; Func <string, TransportProducer> producerFactory = partition => { wasFactoryCalled = true; return(transportProducer); }; var transportProducerPool = new TransportProducerPool(producerFactory, pool: startingPool, eventHubProducer: transportProducer); // Validate the initial state. Assert.That(startingPool.TryGetValue("0", out _), Is.True, "The requested partition should appear in the pool."); Assert.That(wasFactoryCalled, Is.False, "No producer should not have been created."); Assert.That(transportProducer.CloseCallCount, Is.EqualTo(0), "The producer should not have been closed."); // Expire the producer and validate the removal state. await transportProducerPool.ExpirePooledProducerAsync("0"); Assert.That(startingPool.TryGetValue("0", out _), Is.False, "The requested partition should have been removed."); Assert.That(transportProducer.CloseCallCount, Is.EqualTo(1), "The producer should have been closed."); Assert.That(wasFactoryCalled, Is.False, "The requested partition should not have been created."); // Request the producer again and validate a new producer is created. Assert.That(transportProducerPool.GetPooledProducer("0"), Is.Not.Null, "The requested partition should be available."); Assert.That(wasFactoryCalled, Is.True, "A new producer for the requested partition should have been created."); }
public async Task TransportProducerPoolTracksAProducerUsage() { DateTimeOffset oneMinuteAgo = DateTimeOffset.UtcNow.Subtract(TimeSpan.FromMinutes(1)); var transportProducer = new ObservableTransportProducerMock(); var connection = new MockConnection(() => transportProducer); var retryPolicy = new EventHubProducerClientOptions().RetryOptions.ToRetryPolicy(); var startingPool = new ConcurrentDictionary <string, TransportProducerPool.PoolItem> { // An expired item in the pool ["0"] = new TransportProducerPool.PoolItem("0", transportProducer, removeAfter: oneMinuteAgo) }; TransportProducerPool transportProducerPool = new TransportProducerPool(connection, retryPolicy, startingPool); var pooledProducer = transportProducerPool.GetPooledProducer("0"); startingPool.TryGetValue("0", out var poolItem); await using (pooledProducer) { Assert.That(poolItem.ActiveInstances.Count, Is.EqualTo(1), "The usage of a transport producer should be tracked."); } Assert.That(poolItem.ActiveInstances.Count, Is.EqualTo(0), "After usage an active instance should be removed from the pool."); }
/// <summary> /// Gets the routine responsible of finding expired producers. /// </summary> /// private static TimerCallback GetExpirationCallBack(TransportProducerPool pool) => (TimerCallback) typeof(TransportProducerPool) .GetMethod("CreateExpirationTimerCallback", BindingFlags.NonPublic | BindingFlags.Instance) .Invoke(pool, null);