Reservoir 0.1.43

There is a newer version of this package available.
See the version list below for details.
dotnet add package Reservoir --version 0.1.43
                    
NuGet\Install-Package Reservoir -Version 0.1.43
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Reservoir" Version="0.1.43">
  <PrivateAssets>all</PrivateAssets>
  <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
</PackageReference>
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Reservoir" Version="0.1.43" />
                    
Directory.Packages.props
<PackageReference Include="Reservoir">
  <PrivateAssets>all</PrivateAssets>
  <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
</PackageReference>
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add Reservoir --version 0.1.43
                    
#r "nuget: Reservoir, 0.1.43"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package Reservoir@0.1.43
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=Reservoir&version=0.1.43
                    
Install as a Cake Addin
#tool nuget:?package=Reservoir&version=0.1.43
                    
Install as a Cake Tool

Reservoir

Reservoir provides bounded, thread-safe object pooling with an allocation-free warm rent/return path.

var pool = new ObjectPool<MyBuffer, MyBufferPolicy>(maxCapacity: 64);
MyBuffer buffer = pool.Rent();

try
{
    // Use buffer.
}
finally
{
    pool.Return(buffer);
}

Implement IPooledObjectPolicy<T> on a struct to let the JIT specialize and inline policy calls. Create() supplies an object when the pool is empty. TryReset() prepares a returned object for reuse; returning false discards it. Destroy() defaults to calling IDisposable.Dispose() and can be overridden when permanent destruction needs different behavior.

Types designed for pooling can implement IResettable and use the constrained built-in policy. This avoids runtime type checks on the rent/return path:

var pool = new ObjectPool<MyResettableBuffer, ResettablePooledObjectPolicy<MyResettableBuffer>>();

Discarded objects are disposed when they implement IDisposable. Clear() drains retained objects and disposes them while leaving the pool usable. Dispose() drains and permanently closes the pool; later returns are disposed immediately and later rents throw ObjectDisposedException.

Returning an object transfers ownership to the pool. Do not access it afterward and never return it twice. Another thread may rent it immediately after Return() completes.

Debug builds detect objects returned twice or returned to the wrong pool and throw InvalidOperationException. They also report rentals that become unreachable without being returned, including the rent-site stack trace, through Trace and ObjectPoolDiagnostics.LeakDetected. Define RESERVOIR_DIAGNOSTICS to enable the same checks in a Release or staging build. Diagnostics are compiled out when neither DEBUG nor RESERVOIR_DIAGNOSTICS is defined, leaving no fields or calls on the Release hot path.

Pools retain at most maxCapacity objects. Default capacity is Math.Max(32, 2 * Environment.ProcessorCount). Size capacity for peak simultaneous holders, not request rate. When callers hold objects across await, use peak in-flight operations rather than processor count.

Use a scoped lease when the rental does not cross an await. Disposing the stack-only lease returns its value automatically:

using var lease = pool.RentScoped();
MyBuffer buffer = lease.Value;
// Use buffer only while lease is alive.

For a direct local without a separate Value access:

using var lease = pool.RentScoped(out MyBuffer buffer);

Cancellation token sources

CancellationTokenSourcePool reuses sources only when CancellationTokenSource.TryReset() confirms cancellation never fired. Canceled sources are disposed and discarded. Timers and callbacks from an unfired rental are removed before reuse.

CancellationTokenSourcePool pool = CancellationTokenSourcePool.Shared;
using CancellationTokenSource source = pool.Rent();
source.CancelAfter(TimeSpan.FromSeconds(30));
// Use source.Token.

Each rented source returns to its originating pool when disposed. Dispose it only after becoming its sole owner again: no outstanding token readers and no concurrent Cancel, CancelAfter, registration, or disposal operation may remain. Disposal races unsafely with those operations because TryReset() is not thread-safe with concurrent use. Disposal transfers ownership to the pool; dispose each rental exactly once, and do not use or dispose another alias afterward. Linked sources created by CancellationTokenSource.CreateLinkedTokenSource are ordinary sources; dispose them normally.

Dedicated pools own their retained sources. Call Clear() to release them while keeping the pool usable, or dispose the pool when its lifetime ends. Disposing the process-wide shared pool only clears its retained sources; it does not close the pool.

Benchmarks

Run the full .NET 10 suite in Release mode:

dotnet run -c Release --project benchmarks/Reservoir.Benchmarks

Every warm pool path measured 0 B allocated per operation. Results below used BenchmarkDotNet 0.15.8's ShortRun job on .NET 10.0.10, Windows 11, and an Intel Core i7-12700K. Nanosecond results vary by machine; compare methods within a table.

Core pool

The payload owns a 256-byte buffer. Lower ratio is better; new is the baseline.

Method Mean Ratio Allocated
new 12.67 ns 1.00 304 B
Reservoir 11.83 ns 0.93 0 B
Microsoft.Extensions.ObjectPool 14.56 ns 1.15 0 B
ConcurrentBag<T> pool 39.48 ns 3.12 0 B

Warm pool allocation guarantee

Pool Mean Allocated
ObjectPool 11.52 ns 0 B
ListPool 13.13 ns 0 B
DictionaryPool 12.21 ns 0 B
HashSetPool 15.04 ns 0 B
QueuePool 13.62 ns 0 B
StackPool 13.86 ns 0 B
StringBuilderPool 14.96 ns 0 B

Specialized workloads

Workload Baseline Reservoir Baseline allocated Reservoir allocated
StringBuilder, append 128 chars 25.66 ns 18.12 ns 400 B 0 B
List<int>, 8 items 15.27 ns 32.12 ns 88 B 0 B
List<int>, 128 items 139.23 ns 126.66 ns 568 B 0 B
List<int>, 2,048 items 1,738.18 ns 1,502.78 ns 8,248 B 0 B

The single-thread TLS StringBuilder cache measured 5.52 ns and 0 B, as expected; it trades away cross-thread reuse and bounded shared capacity. Scoped leases measured 12.67 ns versus 10.50 ns for manual rent/return, with 0 B allocated on both paths.

Raw Markdown, CSV, and HTML exports, including 1-32 worker contention results, are checked in under benchmarks/results/20260811-200439.

Product Compatible and additional computed target framework versions.
.NET net10.0 is compatible.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

This package has no dependencies.

NuGet packages (3)

Showing the top 3 NuGet packages that depend on Reservoir:

Package Downloads
Dekaf

High-performance, pure C# Apache Kafka client library for .NET

Kevlar

Kevlar is a fast, allocation-conscious resilience library for .NET: retries, circuit breakers, timeouts, rate limiting, concurrency limiting, hedging and fallbacks, composed through one fluent Shield API.

Respire

Fast, modern RESP client for Redis, Valkey, KeyDB, and other RESP-compatible servers.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
1.4.0 14,177 8/13/2026
1.3.0 144 8/13/2026
1.2.3 185 8/13/2026
1.2.0 104 8/13/2026
1.1.2 110 8/12/2026
1.1.0 97 8/12/2026
1.0.1 120 8/12/2026
0.3.5 115 8/12/2026
0.3.3 91 8/12/2026
0.3.0 101 8/12/2026
0.2.0 111 8/11/2026
0.1.43 103 8/11/2026
0.1.35 94 8/11/2026