From b276766d1db43c4b1c4957bf2d3e2c7d460fff9c Mon Sep 17 00:00:00 2001 From: mtvirriel96 Date: Sun, 15 Sep 2024 17:33:06 -0300 Subject: [PATCH 1/3] Adding object pool identified by ConnectionKey --- src/Pool.Tests/ConnectionPoolTests.cs | 65 +++++++ src/Pool.Tests/Fakes/EchoConnectionFactory.cs | 20 +++ src/Pool.Tests/Startup.cs | 2 +- ...onnectionPool{TConnectionKey TPoolItem}.cs | 159 ++++++++++++++++++ ...ationStrategy{TConnectionKeyTPoolItem} .cs | 10 ++ ...onnectionPool{TConnectionKey TPoolItem}.cs | 67 ++++++++ ...rationStrategy{TConnectionKeyTPoolItem}.cs | 29 ++++ src/Pool/ServiceCollectionExtensions.cs | 8 +- 8 files changed, 358 insertions(+), 2 deletions(-) create mode 100644 src/Pool.Tests/ConnectionPoolTests.cs create mode 100644 src/Pool.Tests/Fakes/EchoConnectionFactory.cs create mode 100644 src/Pool/ConnectionPool{TConnectionKey TPoolItem}.cs create mode 100644 src/Pool/DefaultStrategies/DefaultPreparationStrategy{TConnectionKeyTPoolItem} .cs create mode 100644 src/Pool/IConnectionPool{TConnectionKey TPoolItem}.cs create mode 100644 src/Pool/IPreparationStrategy{TConnectionKeyTPoolItem}.cs diff --git a/src/Pool.Tests/ConnectionPoolTests.cs b/src/Pool.Tests/ConnectionPoolTests.cs new file mode 100644 index 0000000..b44676e --- /dev/null +++ b/src/Pool.Tests/ConnectionPoolTests.cs @@ -0,0 +1,65 @@ + +using Pool.Tests.Fakes; + +namespace Pool.Tests; +public sealed class ConnectionPoolTests(IConnectionPool pool) +{ + + [Fact] + public void Pool_Is_Injected() => Assert.NotNull(pool); + + [Fact] + public async Task Lease_And_Release() + { + Assert.Equal(0, pool.UniqueLeases); + var instance = await pool.LeaseAsync("testing", CancellationToken.None); + Assert.NotNull(instance); + Assert.Equal(1, pool.UniqueLeases); + await pool.ReleaseAsync("testing", instance, CancellationToken.None); + } + + [Fact] + public async Task Lease_Unique_Request() + { + var instance1 = await pool.LeaseAsync("testing", CancellationToken.None); + Assert.Equal(1, pool.UniqueLeases); + + var task = pool.LeaseAsync("testing", CancellationToken.None); + Assert.Equal(1, pool.UniqueLeases); + + await pool.ReleaseAsync("testing", instance1, CancellationToken.None); + Assert.Equal(1, pool.UniqueLeases); + + var instance2 = await task; + Assert.NotNull(instance2); + + await pool.ReleaseAsync("testing", instance2, CancellationToken.None); + } + + [Fact] + public async Task Lease_Returns_Ready_Item() + { + var instance = await pool.LeaseAsync("testing", CancellationToken.None); + + Assert.True(instance.IsConnected); + + await pool.ReleaseAsync("testing", instance, CancellationToken.None); + } + + [Fact] + public async Task Queued_Request_Timesout() + { + var instance1 = await pool.LeaseAsync("testing", CancellationToken.None); + Assert.Equal(1, pool.UniqueLeases); + try + { + var execption = await Assert + .ThrowsAsync(async () => + await pool.LeaseAsync("testing", CancellationToken.None)); + } + finally + { + await pool.ReleaseAsync("testing", instance1, CancellationToken.None); + } + } +} diff --git a/src/Pool.Tests/Fakes/EchoConnectionFactory.cs b/src/Pool.Tests/Fakes/EchoConnectionFactory.cs new file mode 100644 index 0000000..6323669 --- /dev/null +++ b/src/Pool.Tests/Fakes/EchoConnectionFactory.cs @@ -0,0 +1,20 @@ + +namespace Pool.Tests.Fakes; + +internal sealed class EchoConnectionFactory + : IItemFactory + , IPreparationStrategy +{ + [System.Diagnostics.CodeAnalysis.SuppressMessage("IDisposableAnalyzers.Correctness", "IDISP005:Return type should indicate that the value should be disposed", Justification = "items created by the factory are disposed by the pool or the factory")] + public IEcho CreateItem() => new Echo(); + + public ValueTask IsReadyAsync( + string connectionKey, + IEcho item, + CancellationToken cancellationToken) => ValueTask.FromResult(item.IsConnected); + + public Task PrepareAsync( + string connectionKey, + IEcho item, + CancellationToken cancellationToken) => item.ConnectAsync(cancellationToken); +} diff --git a/src/Pool.Tests/Startup.cs b/src/Pool.Tests/Startup.cs index bdd0e3b..0175dea 100644 --- a/src/Pool.Tests/Startup.cs +++ b/src/Pool.Tests/Startup.cs @@ -20,5 +20,5 @@ public sealed class Startup .Build(); public void ConfigureServices(IServiceCollection services) => _ = services - .AddTestPool(configuration); + .AddTestPool(configuration); } diff --git a/src/Pool/ConnectionPool{TConnectionKey TPoolItem}.cs b/src/Pool/ConnectionPool{TConnectionKey TPoolItem}.cs new file mode 100644 index 0000000..f1f8a94 --- /dev/null +++ b/src/Pool/ConnectionPool{TConnectionKey TPoolItem}.cs @@ -0,0 +1,159 @@ +using System.Collections.Concurrent; +using System.Runtime.CompilerServices; + +namespace Pool; + +/// > +public sealed class ConnectionPool + : IConnectionPool + , IDisposable + where TConnectionKey : class + where TPoolItem : class +{ + + private static readonly bool IsPoolItemDisposable = typeof(TPoolItem).GetInterface(nameof(IDisposable), true) is not null; + + private readonly ConcurrentDictionary> items = new(); + private readonly PoolOptions poolOptions; + private readonly bool preparationRequired; + private readonly IItemFactory itemFactory; + private readonly IPreparationStrategy? preparationStrategy; + private readonly IPreparationStrategy? connectionpreparationStrategy; + private readonly TimeSpan preparationTimeout; + private bool disposed; + + /// + /// ctor + /// + /// + /// + public ConnectionPool( + IItemFactory itemFactory, + PoolOptions options) + : this(itemFactory, null, null, options) + { } + + /// + /// ctor + /// + /// + /// + /// + /// + /// + public ConnectionPool( + IItemFactory itemFactory, + IPreparationStrategy? preparationStrategy, + IPreparationStrategy? connectionpreparationStrategy, + PoolOptions options) + { + this.itemFactory = itemFactory ?? throw new ArgumentNullException(nameof(itemFactory)); + + preparationRequired = preparationStrategy is not null; + this.preparationStrategy = preparationStrategy; + this.connectionpreparationStrategy = connectionpreparationStrategy; + poolOptions = options; + preparationTimeout = options?.PreparationTimeout ?? Timeout.InfiniteTimeSpan; + } + + /// > + public int UniqueLeases { get; private set; } + + /// > + public int ItemsAvailable => items.Count; + + /// > + public int QueuedLeases => items.Select(x => x.Value).Select(x => x.QueuedLeases).Sum(); + + /// > + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ValueTask LeaseAsync(TConnectionKey connectionKey) => LeaseAsync(connectionKey, CancellationToken.None); + + /// > + public async ValueTask LeaseAsync(TConnectionKey connectionKey, CancellationToken cancellationToken) + { + _ = ThrowIfDisposed().TryAcquireItem(connectionKey, out var item); + + var pooItem = await LeasePoolItemAsync(item, cancellationToken); + return await EnsurePreparedAsync(connectionKey, pooItem, cancellationToken); + } + + /// > + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Task ReleaseAsync(TConnectionKey connectionKey, TPoolItem item) => ReleaseAsync(connectionKey, item, CancellationToken.None); + + /// > + public async Task ReleaseAsync( + TConnectionKey connectionKey, + TPoolItem item, + CancellationToken cancellationToken) => await items[connectionKey].ReleaseAsync(item, cancellationToken); + + /// > + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Task ClearAsync() => ClearAsync(CancellationToken.None); + + /// > + public async Task ClearAsync(CancellationToken cancellationToken) + { + var tasks = items.Select(x => x.Value.ClearAsync(cancellationToken)); + + await Task.WhenAll(tasks); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private bool TryAcquireItem(TConnectionKey connectionKey, out Pool item) => + TryGetItem(connectionKey, out item) || TryCreateItem(connectionKey, out item); + + private bool TryCreateItem(TConnectionKey connectionKey, out Pool item) + { + lock (this) + { + item = new Pool(itemFactory, preparationStrategy, poolOptions); + ++UniqueLeases; + _ = items.TryAdd(connectionKey, item); + return true; + } + } + + private bool TryGetItem(TConnectionKey connectionKey, out Pool item) => items.TryGetValue(connectionKey, out item!); + + private static async ValueTask LeasePoolItemAsync(Pool pool, CancellationToken cancellationToken) + => await pool.LeaseAsync(cancellationToken); + + private async ValueTask EnsurePreparedAsync( + TConnectionKey connectionKey, + TPoolItem item, + CancellationToken cancellationToken) + { + using var timeoutCts = new CancellationTokenSource(preparationTimeout); + using var linkedCts = CancellationTokenSource.CreateLinkedTokenSource( + timeoutCts.Token, + cancellationToken); + cancellationToken = linkedCts.Token; + + if (await connectionpreparationStrategy!.IsReadyAsync(connectionKey, item, cancellationToken)) + { + return item; + } + + await connectionpreparationStrategy.PrepareAsync(connectionKey, item, cancellationToken); + + return item; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private ConnectionPool ThrowIfDisposed() => disposed + ? throw new ObjectDisposedException(nameof(ConnectionPool)) + : this; + + /// + public void Dispose() + { + if (disposed) + { + return; + } + + disposed = true; + } +} diff --git a/src/Pool/DefaultStrategies/DefaultPreparationStrategy{TConnectionKeyTPoolItem} .cs b/src/Pool/DefaultStrategies/DefaultPreparationStrategy{TConnectionKeyTPoolItem} .cs new file mode 100644 index 0000000..162a2ed --- /dev/null +++ b/src/Pool/DefaultStrategies/DefaultPreparationStrategy{TConnectionKeyTPoolItem} .cs @@ -0,0 +1,10 @@ +namespace Pool.DefaultStrategies; + +internal sealed class DefaultPreparationStrategy : IPreparationStrategy + where TConnectionKey : class + where TPoolItem : class +{ + public ValueTask IsReadyAsync(TConnectionKey connectionKey, TPoolItem item, CancellationToken cancellationToken) => ValueTask.FromResult(true); + + public Task PrepareAsync(TConnectionKey connectionKey, TPoolItem item, CancellationToken cancellationToken) => Task.CompletedTask; +} diff --git a/src/Pool/IConnectionPool{TConnectionKey TPoolItem}.cs b/src/Pool/IConnectionPool{TConnectionKey TPoolItem}.cs new file mode 100644 index 0000000..72e6d54 --- /dev/null +++ b/src/Pool/IConnectionPool{TConnectionKey TPoolItem}.cs @@ -0,0 +1,67 @@ +namespace Pool; + +/// +/// pool +/// +/// +/// +public interface IConnectionPool + where TConnectionKey : class + where TPoolItem : class +{ + /// + /// clears the pool and sets allocated to zero + /// + Task ClearAsync(); + + /// + /// clears the pool and sets allocated to zero + /// + Task ClearAsync(CancellationToken cancellationToken); + + /// + /// Simple lease. + /// Check the connection pool, to see if there is an existing entry for that connection key, if there is one, it retrieves an item from the pool + /// identified by that connection, if there is no entry for that connectionKey it will create a new one and retrieve the item from the pool this is while there is capacity available + /// for that pool, if there is no items available for that request it waits forever. + /// waits forever. + /// + /// item from the pool + ValueTask LeaseAsync(TConnectionKey connectionKey); + + /// + /// Simple lease. + /// Check the connection pool, to see if there is an existing entry for that connection key, if there is one, it retrieves an item from the pool + /// identified by that connection, if there is no entry for that connectionKey it will create a new one and retrieve the item from the pool this is while there is capacity available + /// for that pool, if there is no items available for that request it waits forever. + /// + /// + /// + /// item from the pool + ValueTask LeaseAsync(TConnectionKey connectionKey, CancellationToken cancellationToken); + + /// + /// returns an item to the pool that is identified by the connectionKey + /// + /// + /// + Task ReleaseAsync(TConnectionKey connectionKey, TPoolItem item); + + /// + /// returns an item to the pool that is identified by the connectionKey + /// + /// + /// + /// + Task ReleaseAsync(TConnectionKey connectionKey, TPoolItem item, CancellationToken cancellationToken); + + /// + /// returns how many items are currently allocated by the pool + /// + int ItemsAvailable { get; } + + /// + /// returns how many items are currently leased + /// + int UniqueLeases { get; } +} diff --git a/src/Pool/IPreparationStrategy{TConnectionKeyTPoolItem}.cs b/src/Pool/IPreparationStrategy{TConnectionKeyTPoolItem}.cs new file mode 100644 index 0000000..03d3447 --- /dev/null +++ b/src/Pool/IPreparationStrategy{TConnectionKeyTPoolItem}.cs @@ -0,0 +1,29 @@ +namespace Pool; +/// +/// IPreparationStrategy is an interface for preparing pool items before the pool leases them to the caller. +/// +/// +/// +public interface IPreparationStrategy + where TConnectionKey : class + where TPoolItem : class +{ + /// + /// IsReadyAsync checks if the pool item is ready before the pool leases it to the caller. + /// + /// + /// + /// + /// true if the pool item is ready. + ValueTask IsReadyAsync(TConnectionKey connectionKey, TPoolItem item, CancellationToken cancellationToken); + + /// + /// PrepareAsync makes the pool item ready before the pool leases it to the caller. + /// + /// + /// + /// + /// + /// The pool will call PrepareAsync when IsReadyAsync returns false. Implement PrepareAsync to initialize an object, or establish a connection, like connecting to a database or smtp server. + Task PrepareAsync(TConnectionKey connectionKey, TPoolItem item, CancellationToken cancellationToken); +} diff --git a/src/Pool/ServiceCollectionExtensions.cs b/src/Pool/ServiceCollectionExtensions.cs index d7a5b87..40943bd 100644 --- a/src/Pool/ServiceCollectionExtensions.cs +++ b/src/Pool/ServiceCollectionExtensions.cs @@ -104,13 +104,17 @@ public static IServiceCollection AddPool( [UnconditionalSuppressMessage("AOT", "IL3050:Calling members annotated with 'RequiresDynamicCodeAttribute' may break functionality when AOT compiling.", Justification = "the case is handled in the conditional compile directives above")] internal static IServiceCollection AddTestPool< TPoolItem, + TConnectionKey, [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] TFactoryImplementation, - [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] TPreparationStrategy>( + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] TPreparationStrategy, + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] TConnectionPreparationStrategy>( this IServiceCollection services, IConfiguration configuration) where TPoolItem : class + where TConnectionKey : class where TFactoryImplementation : class, IItemFactory where TPreparationStrategy : class, IPreparationStrategy + where TConnectionPreparationStrategy : class, IPreparationStrategy { ArgumentNullException.ThrowIfNull(services); ArgumentNullException.ThrowIfNull(configuration); @@ -118,7 +122,9 @@ internal static IServiceCollection AddTestPool< services.TryAddSingleton(configuration.GetSection(nameof(PoolOptions)).Get() ?? new PoolOptions()); services.TryAddTransient, TFactoryImplementation>(); services.TryAddTransient, TPreparationStrategy>(); + services.TryAddTransient, TConnectionPreparationStrategy>(); services.TryAddTransient, Pool>(); + services.TryAddTransient, ConnectionPool>(); return services; } From f453a7adf3a834dcac97510c1e9ce5b525129e85 Mon Sep 17 00:00:00 2001 From: mtvirriel96 Date: Thu, 24 Oct 2024 22:49:52 -0300 Subject: [PATCH 2/3] Name updates --- ...ConnectionFactory.cs => EchoMapFactory.cs} | 2 +- ...ConnectionPoolTests.cs => PoolMapTests.cs} | 2 +- src/Pool.Tests/Startup.cs | 2 +- ...ationStrategy{TConnectionKeyTPoolItem} .cs | 10 --- ...DefaultPreparationStrategy{TKey TPool} .cs | 10 +++ ...onnectionPool{TConnectionKey TPoolItem}.cs | 67 ----------------- src/Pool/IItemFactory{TPoolItem}.cs | 4 +- src/Pool/IPoolMap{TKey TPool}.cs | 62 +++++++++++++++ src/Pool/IPool{TPoolItem}.cs | 6 +- ...parationStrategy{TConnectionKey TPool}.cs} | 24 +++--- src/Pool/IPreparationStrategy{TPoolItem}.cs | 2 +- ...y TPoolItem}.cs => PoolMap{TKey TPool}.cs} | 75 +++++++++---------- src/Pool/PoolOptions.cs | 4 +- src/Pool/ServiceCollectionExtensions.cs | 14 ++-- 14 files changed, 139 insertions(+), 145 deletions(-) rename src/Pool.Tests/Fakes/{EchoConnectionFactory.cs => EchoMapFactory.cs} (94%) rename src/Pool.Tests/{ConnectionPoolTests.cs => PoolMapTests.cs} (96%) delete mode 100644 src/Pool/DefaultStrategies/DefaultPreparationStrategy{TConnectionKeyTPoolItem} .cs create mode 100644 src/Pool/DefaultStrategies/DefaultPreparationStrategy{TKey TPool} .cs delete mode 100644 src/Pool/IConnectionPool{TConnectionKey TPoolItem}.cs create mode 100644 src/Pool/IPoolMap{TKey TPool}.cs rename src/Pool/{IPreparationStrategy{TConnectionKeyTPoolItem}.cs => IPreparationStrategy{TConnectionKey TPool}.cs} (54%) rename src/Pool/{ConnectionPool{TConnectionKey TPoolItem}.cs => PoolMap{TKey TPool}.cs} (56%) diff --git a/src/Pool.Tests/Fakes/EchoConnectionFactory.cs b/src/Pool.Tests/Fakes/EchoMapFactory.cs similarity index 94% rename from src/Pool.Tests/Fakes/EchoConnectionFactory.cs rename to src/Pool.Tests/Fakes/EchoMapFactory.cs index 6323669..b586c96 100644 --- a/src/Pool.Tests/Fakes/EchoConnectionFactory.cs +++ b/src/Pool.Tests/Fakes/EchoMapFactory.cs @@ -1,7 +1,7 @@  namespace Pool.Tests.Fakes; -internal sealed class EchoConnectionFactory +internal sealed class EchoMapFactory : IItemFactory , IPreparationStrategy { diff --git a/src/Pool.Tests/ConnectionPoolTests.cs b/src/Pool.Tests/PoolMapTests.cs similarity index 96% rename from src/Pool.Tests/ConnectionPoolTests.cs rename to src/Pool.Tests/PoolMapTests.cs index b44676e..b74a584 100644 --- a/src/Pool.Tests/ConnectionPoolTests.cs +++ b/src/Pool.Tests/PoolMapTests.cs @@ -2,7 +2,7 @@ using Pool.Tests.Fakes; namespace Pool.Tests; -public sealed class ConnectionPoolTests(IConnectionPool pool) +public sealed class PoolMapTests(IPoolMap pool) { [Fact] diff --git a/src/Pool.Tests/Startup.cs b/src/Pool.Tests/Startup.cs index 0175dea..f687a10 100644 --- a/src/Pool.Tests/Startup.cs +++ b/src/Pool.Tests/Startup.cs @@ -20,5 +20,5 @@ public sealed class Startup .Build(); public void ConfigureServices(IServiceCollection services) => _ = services - .AddTestPool(configuration); + .AddTestPool(configuration); } diff --git a/src/Pool/DefaultStrategies/DefaultPreparationStrategy{TConnectionKeyTPoolItem} .cs b/src/Pool/DefaultStrategies/DefaultPreparationStrategy{TConnectionKeyTPoolItem} .cs deleted file mode 100644 index 162a2ed..0000000 --- a/src/Pool/DefaultStrategies/DefaultPreparationStrategy{TConnectionKeyTPoolItem} .cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace Pool.DefaultStrategies; - -internal sealed class DefaultPreparationStrategy : IPreparationStrategy - where TConnectionKey : class - where TPoolItem : class -{ - public ValueTask IsReadyAsync(TConnectionKey connectionKey, TPoolItem item, CancellationToken cancellationToken) => ValueTask.FromResult(true); - - public Task PrepareAsync(TConnectionKey connectionKey, TPoolItem item, CancellationToken cancellationToken) => Task.CompletedTask; -} diff --git a/src/Pool/DefaultStrategies/DefaultPreparationStrategy{TKey TPool} .cs b/src/Pool/DefaultStrategies/DefaultPreparationStrategy{TKey TPool} .cs new file mode 100644 index 0000000..b2d7371 --- /dev/null +++ b/src/Pool/DefaultStrategies/DefaultPreparationStrategy{TKey TPool} .cs @@ -0,0 +1,10 @@ +namespace Pool.DefaultStrategies; + +internal sealed class DefaultPreparationStrategy : IPreparationStrategy + where TKey : class + where TPool : class +{ + public ValueTask IsReadyAsync(TKey connectionKey, TPool item, CancellationToken cancellationToken) => ValueTask.FromResult(true); + + public Task PrepareAsync(TKey connectionKey, TPool item, CancellationToken cancellationToken) => Task.CompletedTask; +} diff --git a/src/Pool/IConnectionPool{TConnectionKey TPoolItem}.cs b/src/Pool/IConnectionPool{TConnectionKey TPoolItem}.cs deleted file mode 100644 index 72e6d54..0000000 --- a/src/Pool/IConnectionPool{TConnectionKey TPoolItem}.cs +++ /dev/null @@ -1,67 +0,0 @@ -namespace Pool; - -/// -/// pool -/// -/// -/// -public interface IConnectionPool - where TConnectionKey : class - where TPoolItem : class -{ - /// - /// clears the pool and sets allocated to zero - /// - Task ClearAsync(); - - /// - /// clears the pool and sets allocated to zero - /// - Task ClearAsync(CancellationToken cancellationToken); - - /// - /// Simple lease. - /// Check the connection pool, to see if there is an existing entry for that connection key, if there is one, it retrieves an item from the pool - /// identified by that connection, if there is no entry for that connectionKey it will create a new one and retrieve the item from the pool this is while there is capacity available - /// for that pool, if there is no items available for that request it waits forever. - /// waits forever. - /// - /// item from the pool - ValueTask LeaseAsync(TConnectionKey connectionKey); - - /// - /// Simple lease. - /// Check the connection pool, to see if there is an existing entry for that connection key, if there is one, it retrieves an item from the pool - /// identified by that connection, if there is no entry for that connectionKey it will create a new one and retrieve the item from the pool this is while there is capacity available - /// for that pool, if there is no items available for that request it waits forever. - /// - /// - /// - /// item from the pool - ValueTask LeaseAsync(TConnectionKey connectionKey, CancellationToken cancellationToken); - - /// - /// returns an item to the pool that is identified by the connectionKey - /// - /// - /// - Task ReleaseAsync(TConnectionKey connectionKey, TPoolItem item); - - /// - /// returns an item to the pool that is identified by the connectionKey - /// - /// - /// - /// - Task ReleaseAsync(TConnectionKey connectionKey, TPoolItem item, CancellationToken cancellationToken); - - /// - /// returns how many items are currently allocated by the pool - /// - int ItemsAvailable { get; } - - /// - /// returns how many items are currently leased - /// - int UniqueLeases { get; } -} diff --git a/src/Pool/IItemFactory{TPoolItem}.cs b/src/Pool/IItemFactory{TPoolItem}.cs index 36680bf..d59d74b 100644 --- a/src/Pool/IItemFactory{TPoolItem}.cs +++ b/src/Pool/IItemFactory{TPoolItem}.cs @@ -3,7 +3,7 @@ namespace Pool; /// -/// IPoolItemFactory creates pool items. +/// IPoolItemFactory creates pool pools. /// /// /// Implement your own factory, or use the . @@ -13,6 +13,6 @@ public interface IItemFactory /// /// CreateItem returns a new pool item instance. /// - /// TPoolItem + /// TPool TPoolItem CreateItem(); } diff --git a/src/Pool/IPoolMap{TKey TPool}.cs b/src/Pool/IPoolMap{TKey TPool}.cs new file mode 100644 index 0000000..f935267 --- /dev/null +++ b/src/Pool/IPoolMap{TKey TPool}.cs @@ -0,0 +1,62 @@ +namespace Pool; + +/// +/// pool +/// +/// +/// +public interface IPoolMap + where TKey : class + where TPool : class +{ + /// + /// clears the pool and sets allocated to zero + /// + Task ClearAsync(); + + /// + /// clears the pool and sets allocated to zero + /// + Task ClearAsync(CancellationToken cancellationToken); + + /// + /// Simple lease. + /// Check the connection pool, to see if there is an existing entry for that connection key, if there is one, it retrieves an item from the pool + /// identified by that connection, if there is no entry for that key it will create a new one and retrieve the item from the pool this is while there is capacity available + /// for that pool, if there is no pools available for that request it waits forever. + /// waits forever. + /// + /// item from the pool + ValueTask LeaseAsync(TKey key); + + /// + /// Simple lease. + /// Check the connection pool, to see if there is an existing entry for that connection key, if there is one, it retrieves an item from the pool + /// identified by that connection, if there is no entry for that key it will create a new one and retrieve the item from the pool this is while there is capacity available + /// for that pool, if there is no pools available for that request it waits forever. + /// + /// + /// + /// item from the pool + ValueTask LeaseAsync(TKey key, CancellationToken cancellationToken); + + /// + /// returns a pool identfied by the key + /// + /// + /// + Task ReleaseAsync(TKey key, TPool pool); + + /// + /// returns an item to the pool that is identified by the key + /// + /// + /// + /// + Task ReleaseAsync(TKey key, TPool pool, CancellationToken cancellationToken); + + /// + /// returns how many pools are currently leased + /// + int UniqueLeases { get; } +} diff --git a/src/Pool/IPool{TPoolItem}.cs b/src/Pool/IPool{TPoolItem}.cs index 454601f..86dd477 100644 --- a/src/Pool/IPool{TPoolItem}.cs +++ b/src/Pool/IPool{TPoolItem}.cs @@ -47,17 +47,17 @@ public interface IPool Task ReleaseAsync(TPoolItem item, CancellationToken cancellationToken); /// - /// returns how many items are currently allocated by the pool + /// returns how many pools are currently allocated by the pool /// int ItemsAllocated { get; } /// - /// returns the how many items are of allocated but not leased + /// returns the how many pools are of allocated but not leased /// int ItemsAvailable { get; } /// - /// returns how many items are currently leased + /// returns how many pools are currently leased /// int ActiveLeases { get; } diff --git a/src/Pool/IPreparationStrategy{TConnectionKeyTPoolItem}.cs b/src/Pool/IPreparationStrategy{TConnectionKey TPool}.cs similarity index 54% rename from src/Pool/IPreparationStrategy{TConnectionKeyTPoolItem}.cs rename to src/Pool/IPreparationStrategy{TConnectionKey TPool}.cs index 03d3447..6df574d 100644 --- a/src/Pool/IPreparationStrategy{TConnectionKeyTPoolItem}.cs +++ b/src/Pool/IPreparationStrategy{TConnectionKey TPool}.cs @@ -1,29 +1,29 @@ namespace Pool; /// -/// IPreparationStrategy is an interface for preparing pool items before the pool leases them to the caller. +/// IPreparationStrategy is an interface for preparing pool pools before the pool leases them to the caller. /// -/// -/// -public interface IPreparationStrategy - where TConnectionKey : class - where TPoolItem : class +/// +/// +public interface IPreparationStrategy + where TKey : class + where TPool : class { /// /// IsReadyAsync checks if the pool item is ready before the pool leases it to the caller. /// - /// - /// + /// + /// /// /// true if the pool item is ready. - ValueTask IsReadyAsync(TConnectionKey connectionKey, TPoolItem item, CancellationToken cancellationToken); + ValueTask IsReadyAsync(TKey key, TPool pool, CancellationToken cancellationToken); /// /// PrepareAsync makes the pool item ready before the pool leases it to the caller. /// - /// - /// + /// + /// /// /// /// The pool will call PrepareAsync when IsReadyAsync returns false. Implement PrepareAsync to initialize an object, or establish a connection, like connecting to a database or smtp server. - Task PrepareAsync(TConnectionKey connectionKey, TPoolItem item, CancellationToken cancellationToken); + Task PrepareAsync(TKey key, TPool pool, CancellationToken cancellationToken); } diff --git a/src/Pool/IPreparationStrategy{TPoolItem}.cs b/src/Pool/IPreparationStrategy{TPoolItem}.cs index 2ae0e30..e03bdcd 100644 --- a/src/Pool/IPreparationStrategy{TPoolItem}.cs +++ b/src/Pool/IPreparationStrategy{TPoolItem}.cs @@ -1,7 +1,7 @@ namespace Pool; /// -/// IPreparationStrategy is an interface for preparing pool items before the pool leases them to the caller. +/// IPreparationStrategy is an interface for preparing pool pools before the pool leases them to the caller. /// /// public interface IPreparationStrategy diff --git a/src/Pool/ConnectionPool{TConnectionKey TPoolItem}.cs b/src/Pool/PoolMap{TKey TPool}.cs similarity index 56% rename from src/Pool/ConnectionPool{TConnectionKey TPoolItem}.cs rename to src/Pool/PoolMap{TKey TPool}.cs index f1f8a94..6190e5d 100644 --- a/src/Pool/ConnectionPool{TConnectionKey TPoolItem}.cs +++ b/src/Pool/PoolMap{TKey TPool}.cs @@ -4,21 +4,21 @@ namespace Pool; /// > -public sealed class ConnectionPool - : IConnectionPool +public sealed class PoolMap + : IPoolMap , IDisposable - where TConnectionKey : class - where TPoolItem : class + where TKey : class + where TPool : class { - private static readonly bool IsPoolItemDisposable = typeof(TPoolItem).GetInterface(nameof(IDisposable), true) is not null; + private static readonly bool IsPoolItemDisposable = typeof(TPool).GetInterface(nameof(IDisposable), true) is not null; - private readonly ConcurrentDictionary> items = new(); + private readonly ConcurrentDictionary> pools = new(); private readonly PoolOptions poolOptions; private readonly bool preparationRequired; - private readonly IItemFactory itemFactory; - private readonly IPreparationStrategy? preparationStrategy; - private readonly IPreparationStrategy? connectionpreparationStrategy; + private readonly IItemFactory itemFactory; + private readonly IPreparationStrategy? preparationStrategy; + private readonly IPreparationStrategy? connectionpreparationStrategy; private readonly TimeSpan preparationTimeout; private bool disposed; @@ -27,8 +27,8 @@ public sealed class ConnectionPool /// /// /// - public ConnectionPool( - IItemFactory itemFactory, + public PoolMap( + IItemFactory itemFactory, PoolOptions options) : this(itemFactory, null, null, options) { } @@ -41,10 +41,10 @@ public ConnectionPool( /// /// /// - public ConnectionPool( - IItemFactory itemFactory, - IPreparationStrategy? preparationStrategy, - IPreparationStrategy? connectionpreparationStrategy, + public PoolMap( + IItemFactory itemFactory, + IPreparationStrategy? preparationStrategy, + IPreparationStrategy? connectionpreparationStrategy, PoolOptions options) { this.itemFactory = itemFactory ?? throw new ArgumentNullException(nameof(itemFactory)); @@ -60,33 +60,30 @@ public ConnectionPool( public int UniqueLeases { get; private set; } /// > - public int ItemsAvailable => items.Count; - - /// > - public int QueuedLeases => items.Select(x => x.Value).Select(x => x.QueuedLeases).Sum(); + public int QueuedLeases => pools.Select(x => x.Value).Select(x => x.QueuedLeases).Sum(); /// > [MethodImpl(MethodImplOptions.AggressiveInlining)] - public ValueTask LeaseAsync(TConnectionKey connectionKey) => LeaseAsync(connectionKey, CancellationToken.None); + public ValueTask LeaseAsync(TKey key) => LeaseAsync(key, CancellationToken.None); /// > - public async ValueTask LeaseAsync(TConnectionKey connectionKey, CancellationToken cancellationToken) + public async ValueTask LeaseAsync(TKey key, CancellationToken cancellationToken) { - _ = ThrowIfDisposed().TryAcquireItem(connectionKey, out var item); + _ = ThrowIfDisposed().TryAcquireItem(key, out var item); var pooItem = await LeasePoolItemAsync(item, cancellationToken); - return await EnsurePreparedAsync(connectionKey, pooItem, cancellationToken); + return await EnsurePreparedAsync(key, pooItem, cancellationToken); } /// > [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Task ReleaseAsync(TConnectionKey connectionKey, TPoolItem item) => ReleaseAsync(connectionKey, item, CancellationToken.None); + public Task ReleaseAsync(TKey key, TPool pool) => ReleaseAsync(key, pool, CancellationToken.None); /// > public async Task ReleaseAsync( - TConnectionKey connectionKey, - TPoolItem item, - CancellationToken cancellationToken) => await items[connectionKey].ReleaseAsync(item, cancellationToken); + TKey key, + TPool pool, + CancellationToken cancellationToken) => await pools[key].ReleaseAsync(pool, cancellationToken); /// > [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -95,34 +92,34 @@ public async Task ReleaseAsync( /// > public async Task ClearAsync(CancellationToken cancellationToken) { - var tasks = items.Select(x => x.Value.ClearAsync(cancellationToken)); + var tasks = pools.Select(x => x.Value.ClearAsync(cancellationToken)); await Task.WhenAll(tasks); } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private bool TryAcquireItem(TConnectionKey connectionKey, out Pool item) => + private bool TryAcquireItem(TKey connectionKey, out Pool item) => TryGetItem(connectionKey, out item) || TryCreateItem(connectionKey, out item); - private bool TryCreateItem(TConnectionKey connectionKey, out Pool item) + private bool TryCreateItem(TKey connectionKey, out Pool item) { lock (this) { - item = new Pool(itemFactory, preparationStrategy, poolOptions); + item = new Pool(itemFactory, preparationStrategy, poolOptions); ++UniqueLeases; - _ = items.TryAdd(connectionKey, item); + _ = pools.TryAdd(connectionKey, item); return true; } } - private bool TryGetItem(TConnectionKey connectionKey, out Pool item) => items.TryGetValue(connectionKey, out item!); + private bool TryGetItem(TKey connectionKey, out Pool item) => pools.TryGetValue(connectionKey, out item!); - private static async ValueTask LeasePoolItemAsync(Pool pool, CancellationToken cancellationToken) + private static async ValueTask LeasePoolItemAsync(Pool pool, CancellationToken cancellationToken) => await pool.LeaseAsync(cancellationToken); - private async ValueTask EnsurePreparedAsync( - TConnectionKey connectionKey, - TPoolItem item, + private async ValueTask EnsurePreparedAsync( + TKey connectionKey, + TPool item, CancellationToken cancellationToken) { using var timeoutCts = new CancellationTokenSource(preparationTimeout); @@ -142,8 +139,8 @@ private async ValueTask EnsurePreparedAsync( } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private ConnectionPool ThrowIfDisposed() => disposed - ? throw new ObjectDisposedException(nameof(ConnectionPool)) + private PoolMap ThrowIfDisposed() => disposed + ? throw new ObjectDisposedException(nameof(PoolMap)) : this; /// diff --git a/src/Pool/PoolOptions.cs b/src/Pool/PoolOptions.cs index 0ff2d30..0a662f1 100644 --- a/src/Pool/PoolOptions.cs +++ b/src/Pool/PoolOptions.cs @@ -6,13 +6,13 @@ public sealed class PoolOptions { /// - /// MinSize gets or sets the minimum number of items in the pool. + /// MinSize gets or sets the minimum number of pools in the pool. /// /// Defaults to zero. public int MinSize { get; set; } /// - /// MaxSize gets or sets the maximum number of items in the pool. + /// MaxSize gets or sets the maximum number of pools in the pool. /// /// Defaults to Int32.MaxValue public int MaxSize { get; set; } = Int32.MaxValue; diff --git a/src/Pool/ServiceCollectionExtensions.cs b/src/Pool/ServiceCollectionExtensions.cs index 40943bd..e4970cf 100644 --- a/src/Pool/ServiceCollectionExtensions.cs +++ b/src/Pool/ServiceCollectionExtensions.cs @@ -104,17 +104,19 @@ public static IServiceCollection AddPool( [UnconditionalSuppressMessage("AOT", "IL3050:Calling members annotated with 'RequiresDynamicCodeAttribute' may break functionality when AOT compiling.", Justification = "the case is handled in the conditional compile directives above")] internal static IServiceCollection AddTestPool< TPoolItem, - TConnectionKey, + TPool, + TKey, [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] TFactoryImplementation, [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] TPreparationStrategy, - [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] TConnectionPreparationStrategy>( + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] TPoolMapPreparationStrategy>( this IServiceCollection services, IConfiguration configuration) where TPoolItem : class - where TConnectionKey : class + where TPool : class + where TKey : class where TFactoryImplementation : class, IItemFactory where TPreparationStrategy : class, IPreparationStrategy - where TConnectionPreparationStrategy : class, IPreparationStrategy + where TPoolMapPreparationStrategy : class, IPreparationStrategy { ArgumentNullException.ThrowIfNull(services); ArgumentNullException.ThrowIfNull(configuration); @@ -122,9 +124,9 @@ internal static IServiceCollection AddTestPool< services.TryAddSingleton(configuration.GetSection(nameof(PoolOptions)).Get() ?? new PoolOptions()); services.TryAddTransient, TFactoryImplementation>(); services.TryAddTransient, TPreparationStrategy>(); - services.TryAddTransient, TConnectionPreparationStrategy>(); + services.TryAddTransient, TPoolMapPreparationStrategy>(); services.TryAddTransient, Pool>(); - services.TryAddTransient, ConnectionPool>(); + services.TryAddTransient, PoolMap>(); return services; } From 124fd5e5de0e482b1bc2f2489e06d7607470862d Mon Sep 17 00:00:00 2001 From: mtvirriel96 Date: Thu, 24 Oct 2024 22:51:33 -0300 Subject: [PATCH 3/3] One line clearAsync --- src/Pool/PoolMap{TKey TPool}.cs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/Pool/PoolMap{TKey TPool}.cs b/src/Pool/PoolMap{TKey TPool}.cs index 6190e5d..31bfdfe 100644 --- a/src/Pool/PoolMap{TKey TPool}.cs +++ b/src/Pool/PoolMap{TKey TPool}.cs @@ -91,11 +91,7 @@ public async Task ReleaseAsync( /// > public async Task ClearAsync(CancellationToken cancellationToken) - { - var tasks = pools.Select(x => x.Value.ClearAsync(cancellationToken)); - - await Task.WhenAll(tasks); - } + => await Task.WhenAll(pools.Select(x => x.Value.ClearAsync(cancellationToken))); [MethodImpl(MethodImplOptions.AggressiveInlining)] private bool TryAcquireItem(TKey connectionKey, out Pool item) =>