{"id":22937963,"url":"https://github.com/marklauter/pool","last_synced_at":"2026-04-06T03:35:05.042Z","repository":{"id":211660120,"uuid":"720932958","full_name":"marklauter/pool","owner":"marklauter","description":"An object pool for dotnet.","archived":false,"fork":false,"pushed_at":"2025-09-22T19:45:23.000Z","size":378,"stargazers_count":1,"open_issues_count":1,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2026-02-01T11:57:30.769Z","etag":null,"topics":["csharp","pooling"],"latest_commit_sha":null,"homepage":"","language":"C#","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/marklauter.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2023-11-20T02:04:56.000Z","updated_at":"2025-09-22T19:45:27.000Z","dependencies_parsed_at":"2024-05-05T02:34:47.638Z","dependency_job_id":"b681d65b-e233-44c6-85a0-c2232a4b55ec","html_url":"https://github.com/marklauter/pool","commit_stats":null,"previous_names":["marklauter/pool"],"tags_count":21,"template":false,"template_full_name":null,"purl":"pkg:github/marklauter/pool","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/marklauter%2Fpool","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/marklauter%2Fpool/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/marklauter%2Fpool/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/marklauter%2Fpool/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/marklauter","download_url":"https://codeload.github.com/marklauter/pool/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/marklauter%2Fpool/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31458838,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-05T21:22:52.476Z","status":"online","status_checked_at":"2026-04-06T02:00:07.287Z","response_time":112,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["csharp","pooling"],"created_at":"2024-12-14T12:15:18.588Z","updated_at":"2026-04-06T03:35:05.036Z","avatar_url":"https://github.com/marklauter.png","language":"C#","funding_links":[],"categories":[],"sub_categories":[],"readme":"## Build Status\n[![.NET Test](https://github.com/marklauter/pool/actions/workflows/dotnet.tests.yml/badge.svg)](https://github.com/marklauter/pool/actions/workflows/dotnet.tests.yml)\n[![.NET Publish](https://github.com/marklauter/pool/actions/workflows/dotnet.publish.yml/badge.svg)](https://github.com/marklauter/pool/actions/workflows/dotnet.publish.yml)\n[![Nuget](https://img.shields.io/badge/Nuget-v6.0.0-blue)](https://www.nuget.org/packages/MSL.Pool/)\n[![Nuget](https://img.shields.io/badge/.NET-8.0-blue)](https://dotnet.microsoft.com/en-us/download/dotnet/8.0/)\n\n## \n![Pool Logo](https://raw.githubusercontent.com/marklauter/pool/main/images/pool.png \"Pool Logo\")\n\n# Pool\n`IPool\u003cTPoolItem\u003e` is an object pool that uses the lease/release pattern.\nIt allows for, but does not require, [custom preparation strategies](#pool-item-preparation-strategy).\nCommon use cases for [preparation strategies](#pool-item-preparation-strategy) \ninclude objects that benefit from long-lived connections, \nlike SMTP or database connections.\n\n## Github Repository\n[https://github.com/marklauter/pool](https://github.com/marklauter/pool)\n\n## Nuget Package\n### Nuget.org Page\n[https://www.nuget.org/packages/MSL.Pool](https://www.nuget.org/packages/MSL.Pool)\n### Package Install\n```console\ndotnet add package MSL.Pool\n```\n\n## The Lease / Release Pattern\nWhen the pool is instantiated, it creates a minimum number of poolable items and places them in the available items queue.\n\nWhen a lease is requested, the pool attempts to dequeue an item. \nIf an item is returned from the queue, the item is returned on a task.\nHowever, if the item queue is empty, the pool attempts to create a new item to fulfill the lease request.\nIf the pool has reached its allocation limit, it enqueues a new lease request object into the lease request queue.\nThen it returns the least request's `TaskCompletionSource.Task` to the caller.\n\nThe caller will block on await until an item is released back to the pool or until the lease request times out. \nFirst, the release operation attempts to dequeue an active lease request.\nIf a lease request is returned, the least request's task is completed with the item being released by the caller.\nBut if the lease request queue is empty, the item will be placed in the available item queue.\n\n## Pool\n`IPool\u003cTPoolItem\u003e` is implmented _internally_ by `Pool\u003cTPoolItem\u003e`.\n\nYou can access `IPool\u003cTPoolItem\u003e` by registering it with the service collection by calling \none of the `IServiceCollection` extensions from the `Pool.DependencyInjection` namespace.\nSee [Dependency Injection](#dependency-injection) for more information.\n\n`IPool\u003cTPoolItem\u003e` provides three methods with convenient overloads:\n- `LeaseAsync` - returns an item from the pool and optionally [performs a ready check](#pool-item-preparation-strategy)\n- `ReleaseAsync` - returns an item to the pool\n- `ClearAsync` - clears the pool, disposes of items as required, and reinitializes the pool with `PoolOptions.MinSize` items\n\nThe caller is responsible for calling `ReleaseAsync` when it no longer needs the item.\nI recommend using try / finally.\n```csharp\nvar item = await pool.LeaseAsync();\ntry\n{\n    item.DoStuff();\n}\nfinally\n{\n    await pool.ReleaseAsync(item);\n}\n```\n\n`Pool\u003cTPoolItem\u003e` has three dependencies injected into the constructor:\n- `IItemFactory\u003cTPoolItem\u003e`\n- `IPreparationStrategy\u003cTPoolItem\u003e`\n- `PoolOptions`\n\nSee [Dependency Injection](#dependency-injection) for more information.\n\nThe pool will use an [item factory](#pool-item-factory) to create new items as required.\nDuring the lease operation, the pool invokes a [ready checker](#pool-item-preparation-strategy) \nto initialize an item that isn't ready.\n\n## Pool Item Factory\nImplement the `IItemFactory\u003cTPoolItem\u003e` interface to create new items for the pool. \nThe library provides a default pool implementation that uses `IServiceProvider` to construct items.\nTo use the default implementation, call `AddPool\u003cTPoolItem\u003e` or \n`AddPoolWithDefaultFactory\u003cTPoolItem, TPreparationStrategy\u003e` \nwhen registering the pool with the service collection.\nSee [Dependency Injection](#dependency-injection) for more information.\n\n## Pool Item Preparation Strategy\nImplement the `IPreparationStrategy\u003cTPoolItem\u003e` interface to ensure an item is ready for use when leased from the pool.\n\nThere's a default `IPreparationStrategy\u003cTPoolItem\u003e` implementation that always returns \n`true` from the `IsReadyAsync` method.\nTo use the default implementation with a custom item factory, \ncall `AddPool\u003cTPoolItem\u003e` \nwhen registering the pool with the service collection.\nSee [Dependency Injection](#dependency-injection) for more information.\n\nReady check is useful for items that may become inactive for some time, \nsuch as an SMTP connection that has been idle long enough for the server to terminate\nthe connection.\n\nFor example, if you're implementing an SMTP connection pool, \nthe lease operation can verify the connection to the STMP server \nby invoking the SMTP `no-op`.  You can connect and authenticate to the SMTP server if the ready check fails. \n\nSample SMTP connection ready check implementation using `MailKit.IMailTransport`:\n```csharp\npublic async ValueTask\u003cbool\u003e IsReadyAsync(IMailTransport item, CancellationToken cancellationToken) =\u003e\n    item.IsConnected\n    \u0026\u0026 item.IsAuthenticated\n    \u0026\u0026 await NoOpAsync(item, cancellationToken);\n```\n\nSample SMTP connection `PrepareAsync` implementation using `MailKit.IMailTransport`:\n```csharp\npublic async Task PrepareAsync(IMailTransport item, CancellationToken cancellationToken)\n{\n    await item.ConnectAsync(hostOptions.Host, hostOptions.Port, hostOptions.UseSsl, cancellationToken);\n    await item.AuthenticateAsync(credentials.UserName, credentials.Password, cancellationToken);\n}\n```\n\n## Dependency Injection\nThe `ServiceCollectionExtensions` class is in the `Pool.DependencyInjection` namespace.\n- Call `AddPool\u003cTPoolItem\u003e` to register a singleton pool. Pass `Action\u003cPoolRegistrationOptions\u003e` to specify whether or not to register the default item factory and ready check implementations.\n- Call `AddPreparationStrategy\u003cTPoolItem, TPreparationStrategy\u003e` to register a singleton preparation strategy.\n- Call `AddPoolItemFactory\u003cTPoolItem, TFactoryImplementation\u003e` to register a singleton item factory implementation.\n\n### Sample `AddPool\u003cTPoolItem\u003e` Registration\n```csharp\nservices.AddPool\u003cIMailTransport\u003e(configuration, options =\u003e\n{\n    // use default factory, which uses the service provider to construct pool items\n    options.RegisterDefaultFactory = true;\n});\n```\n\n## Pool Options\n\nThe `PoolOptions` class configures behavior of the pool. These options control pool sizing, timeouts, and factory selection.\n\nYou can configure pool options:\n- When registering a pool with dependency injection\n- By creating a pool options instance and passing it to the pool constructor\n- Through configuration binding from appsettings.json\n\n### Available Options\n\n- `MinSize` - The minimum number of items to maintain in the pool\n  - This is the initial pool size when created\n  - Defaults to `0`\n\n- `MaxSize` - The maximum number of items the pool can create\n  - When reached, lease requests are queued until items are released\n  - Defaults to `Int32.MaxValue`\n\n- `LeaseTimeout` - The maximum time to wait when leasing an item from the pool\n  - When expired, lease throws a `TimeoutException`\n  - Defaults to `Timeout.InfiniteTimeSpan`\n\n- `PreparationTimeout` - The maximum time to wait for item preparation\n  - Controls timeout for ready checking and preparation\n  - Defaults to `Timeout.InfiniteTimeSpan`\n\n- `IdleTimeout` - The maximum time an item can remain idle in the pool\n  - Idle items exceeding this timeout are removed and disposed\n  - Defaults to `Timeout.InfiniteTimeSpan`\n\n- `UseDefaultPreparationStrategy` - Whether to use the default preparation strategy\n  - The default strategy always returns `true` from `IsReadyAsync`\n  - Defaults to `false`\n\n- `UseDefaultFactory` - Whether to use the default item factory\n  - The default factory uses the service provider to construct items\n  - Defaults to `false`\n\n### Sample Configuration\n\n```csharp\n// Register with dependency injection through configuration\nservices.AddPool\u003cMyPoolItem\u003e(configuration)\n\n// or through the configure options action\nservices.AddPool\u003cMyPoolItem\u003e(configuration, options =\u003e\n{\n    options.MinSize = 2;\n    options.MaxSize = 10;\n});\n\n```\n\n## Named Pools\n\nStarting from March 2025, Pool supports creating multiple named instances of pools for the same item type. This allows you to configure different pools with different settings for the same type of item.\n\n### Why Use Named Pools?\n\nNamed pools are useful when you need:\n- Different pool configurations for the same type (different min/max sizes, timeouts, etc.)\n- Dedicated pools for different use cases or components in your application\n- Isolating pool resources for different concerns\n\n### Using Named Pools\n\n#### Basic Named Pool Registration\n\nTo register a named pool and its factory:\n\n**note: the name provided will be converted to a service key in the format `{name}.{typeof(TPoolItem).Name}.pool`**\n\n```csharp\n// Add the pool factory\nservices.AddPoolFactory\u003cMyPoolItem\u003e();\n\n// Add a named pool\nservices.AddNamedPool\u003cMyPoolItem\u003e(\n    \"ReadPool\",\n    configuration,\n    options =\u003e \n    {\n        options.MinSize = 5;\n        options.MaxSize = 20;\n        options.LeaseTimeout = TimeSpan.FromSeconds(30);\n    });\n\n// Add another named pool with different configuration\nservices.AddNamedPool\u003cMyPoolItem\u003e(\n    \"WritePool\",\n    configuration,\n    options =\u003e \n    {\n        options.MinSize = 2;\n        options.MaxSize = 10;\n        options.LeaseTimeout = TimeSpan.FromSeconds(60);\n    });\n```\n\nYou can also register a typed client that will use a specific named pool:\n\n```csharp\n// Register a pool with a typed client\nservices.AddPool\u003cMyPoolItem, MyPoolClient\u003e(\n    configuration,\n    options =\u003e \n    {\n        options.MinSize = 5;\n        options.MaxSize = 20;\n    },\n    client =\u003e \n    {\n        // Configure the pool client if needed\n    });\n```\n\nInject the IPoolFactory\u003cTPoolItem\u003e into your class and create the pool you need:\n\n```csharp\npublic class MyService\n{\n    private readonly IPool\u003cMyPoolItem\u003e readPool;\n    private readonly IPool\u003cMyPoolItem\u003e writePool;\n\n    public MyService(IPoolFactory\u003cMyPoolItem\u003e poolFactory)\n    {\n        readPool = poolFactory.CreatePool(\"ReadPool.MyPoolItem.pool\");\n        writePool = poolFactory.CreatePool(\"WritePool.MyPoolItem.pool\");\n    }\n\n    public async Task DoReadOperationAsync()\n    {\n        var item = await readPool.LeaseAsync();\n        try\n        {\n            // Use the item for read operations\n        }\n        finally\n        {\n            await readPool.ReleaseAsync(item);\n        }\n    }\n\n    public async Task DoWriteOperationAsync()\n    {\n        var item = await writePool.LeaseAsync();\n        try\n        {\n            // Use the item for write operations\n        }\n        finally\n        {\n            await writePool.ReleaseAsync(item);\n        }\n    }\n}\n```\n\n## Metrics\n\n`IPoolMetrics` provides a comprehensive metrics collection system for your pools, allowing you to monitor performance, diagnose issues, and optimize usage patterns. The Pool library includes a default implementation (`DefaultPoolMetrics`) that integrates with .NET's built-in metrics infrastructure.\n\nMetrics are named using the pattern `{poolName}.{metricName}` and include the following:\n\n### Counter Metrics\n- `{name}.lease_exception` - Tracks the number of exceptions thrown during pool item lease operations\n- `{name}.preparation_exception` - Tracks the number of exceptions thrown during pool item preparation\n\n### Histogram Metrics\n- `{name}.lease_wait_time` - Measures the time spent waiting to acquire a pool item (in milliseconds)\n- `{name}.item_preparation_time` - Measures the time spent preparing pool items before use (in milliseconds)\n\n### Observable Metrics\n- `{name}.items_allocated` - Tracks the total number of items allocated in the pool\n- `{name}.items_available` - Tracks the number of items currently available for lease\n- `{name}.active_leases` - Tracks the number of currently active leases\n- `{name}.queued_leases` - Tracks the number of lease requests waiting in the queue\n- `{name}.utilization_rate` - Monitors the pool utilization rate (active leases / total items)\n\n### Using Pool Metrics\n\nPool metrics are automatically enabled when you register a pool with the service collection. The metrics can be consumed by any metrics collector that supports .NET's metrics API, such as OpenTelemetry, Prometheus, or custom exporters.\n\nExample of configuring OpenTelemetry to collect pool metrics:\n\n```csharp\nservices.AddOpenTelemetry()\n    .WithMetrics(builder =\u003e builder\n        // Add your pool metrics to OpenTelemetry\n        .AddMeter(Pool\u003cMyPoolItem\u003e.PoolName)\n        // Configure exporters as needed\n        .AddPrometheusExporter());\n```\n\nYou can also create a custom metrics implementation by implementing the `IPoolMetrics` interface and registering it with the DI container:\n\n```csharp\nservices.AddPool\u003cMyPoolItem\u003e(configuration, options =\u003e\n{\n    options.RegisterDefaultFactory = true;\n})\n.AddSingleton\u003cIPoolMetrics, MyCustomPoolMetrics\u003e();\n```\n\n### Using Metrics with Named Pools\n\nWhen working with named pools, each pool instance will have its own set of metrics with the name pattern `{poolName}.{poolItemType.Name}.Pool`. To collect metrics from named pools, you'll need to ensure you're adding the correct meter name to your metrics system.\n\nExample of configuring OpenTelemetry to collect metrics from a named pool:\n\n```csharp\n// First, register your named pools\n\nservices.AddNamedPool\u003cDatabaseConnection\u003e(\n    \"ReadOnly\",\n    configuration,\n    options =\u003e \n    {\n        options.MinSize = 5;\n        options.MaxSize = 20;\n    });\n\nservices.AddNamedPool\u003cDatabaseConnection\u003e(\n    \"ReadWrite\",\n    configuration,\n    options =\u003e \n    {\n        options.MinSize = 2;\n        options.MaxSize = 10;\n    });\n\nservices.AddOpenTelemetry()\n    .WithMetrics(builder =\u003e builder\n        // Add meters for the named pools\n        .AddMeter($\"ReadOnly.{Pool\u003cDatabaseConnection\u003e.PoolName}\")\n        .AddMeter($\"ReadWrite.{Pool\u003cDatabaseConnection\u003e.PoolName}\")\n        // Configure exporters as needed\n        .AddPrometheusExporter());\n```\n\nWith this configuration, your metrics system will collect separate metrics for each named pool, allowing you to monitor and analyze the performance of individual pools independently.\n\nPool metrics can help you answer important questions about your pool's performance and health:\n- Is the pool sized appropriately for my workload?\n- Are items taking too long to prepare?\n- Are callers waiting too long to acquire items?\n- Is the pool under heavy load or running efficiently?\n\nUsing these metrics, you can fine-tune your pool configuration for optimal performance in your specific scenarios.\n\n## Dev Log\n- 12 FEB 2024 - started SMTP pool at the end of 2023, but got busy with other stuff. I'll take it up again soon though because I need it for a work project.\n- 05 MAY 2024 - prepping the library for Nuget by supporting dotnet 6, 7 and 8.\n- 06 MAY 2024 - published to Nuget.\n- 17 MAY 2024 - added tests for out-of-order disposal scenarios.\n- 17 MAY 2024 - updated `readme.md`\n- 17 MAY 2024 - `Sample/Smtp.Pool` is still a work in progress.\n- 18 MAY 2024 :ALERT: breaking changes.\n- 18 MAY 2024 - refactored dependency injection extensions. \n- 18 MAY 2024 - refactored to use ValueTask on LeaseAsync method. \n- 16 JUL 2024 - better naming and cleaned up smtp sample project.\n- 17 JUL 2024 - added idle timeout\n- XX MAR 2025 - better lease timout handling\n- XX MAR 2025 - added metrics\n- 16 MAR 2025 - added named pools\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmarklauter%2Fpool","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmarklauter%2Fpool","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmarklauter%2Fpool/lists"}