-
Notifications
You must be signed in to change notification settings - Fork 0
Leasing and Releasing
The lease/release pair is the core of the pool: borrow an item, use it, return it. This guide covers the manual try/finally style, the scoped-lease style that automates the return, cancellation and timeouts, and emptying the pool with Clear. For the bare method signatures, see the IPool API reference.
LeaseAsync borrows an item as a ValueTask<TPoolItem>. It reuses an idle item, creates one if the pool is below MaxSize, or waits for a release when the pool is saturated:
var item = await pool.LeaseAsync(cancellationToken);Always return a leased item with Release, and do it from a finally so the return happens even when the work throws:
var item = await pool.LeaseAsync(cancellationToken);
try
{
// use the item
}
finally
{
pool.Release(item);
}Release is synchronous and returns void by design — returning an item enqueues it and frees a capacity permit, neither of which awaits. If a lease is waiting, the item goes to it; otherwise it becomes idle for the next lease.
LeaseScopeAsync wraps the lease in a disposable Lease<TPoolItem>, so a using returns the item on scope exit — no try/finally to write:
using var lease = await pool.LeaseScopeAsync(cancellationToken);
await lease.Item.SendAsync(message, cancellationToken);This is the recommended way to lease. It converts "forgot to release" — which no analyzer sees — into "forgot to dispose," which using and the IDisposable analyzers already guard against. The return is idempotent: a double dispose is a safe no-op, which closes the double-release footgun described in Avoiding footguns.
Lease.Item throws ObjectDisposedException after the lease is disposed, guarding against use-after-return. A lease that is garbage-collected without being disposed is counted on the pool.leases.leaked counter, so leaks stay observable even when they slip through — see Metrics.
The raw LeaseAsync/Release pair stays available for cases a using scope cannot express.
LeaseAsync(cancellationToken) cancels the wait for an item. The two failure modes surface as distinct exceptions:
- A canceled caller token throws
OperationCanceledException. - A wait that exceeds
LeaseTimeoutthrowsTaskCanceledException.
LeaseTimeout defaults to Timeout.InfiniteTimeSpan, so by default a saturated pool waits indefinitely for a release. Set a finite LeaseTimeout in Pool options to bound the wait.
Clear disposes every idle item and refills the pool to MinSize, capped by free capacity so it never exceeds MaxSize. Items currently leased out are untouched and re-enter the pool when released:
pool.Clear();Use it to drop connections you know have gone bad — after a credential rotation, for example — without tearing down the pool itself.
IPool<TPoolItem> exposes counters for diagnostics: ItemsAllocated, ItemsAvailable, ActiveLeases, QueuedLeases, and Name. They are derived from live pool state, so they are cheap to read. For production observability, prefer the instruments in Metrics over polling these.
- Avoiding footguns — release exactly once, never forget, and what dispose does and does not reclaim.
- Preparation strategies — check and restore an item's readiness on each lease.