pools

package module
v0.27.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jun 29, 2026 License: Apache-2.0 Imports: 4 Imported by: 0

README

pools

Documentation

Overview

Package pools provide utilities to recycle allocated objects.

This package provides:

- a generic Pool type that wraps sync.Pool, - a PoolRedeemable variant that hands out a cached redeem closure, - a PoolSlice for recycling slices without juggling pointers.

Debug build

Building with the "poolsdebug" tag (go test -tags poolsdebug ./...) turns on instrumentation that tracks every borrow and redeem and panics on misuse:

- double redeem (including the A -> B -> A case for the redeemable pools), - redeem of a foreign object, - borrow of an object still checked out

It reports the offending call sites.

AssertNoLeaks then reports any object borrowed but never redeemed.

The instrumentation is a no-op with zero overhead when the tag is absent.

Index

Constants

View Source
const DebugBuild = debugBuild

DebugBuild reports whether the pool instrumentation is compiled in (the poolsdebug build tag).

It lets a test that must run in both modes skip the parts that are invalid under instrumentation — e.g. an allocation-count assertion, since the instrumented build allocates a per-borrow tracker.

Variables

This section is empty.

Functions

func AssertNoLeaks

func AssertNoLeaks(TB) bool

AssertNoLeaks reports whether every borrowed object has been redeemed across all pools.

It is only meaningful in the instrumented build (-tags poolsdebug).

In a release build it is a no-op that always reports true, so the same test can run in both modes.

func ResetTracking

func ResetTracking()

ResetTracking clears all recorded borrow/redeem tracking.

This is a no-op in a release build.

Types

type Pool

type Pool[T any] struct {
	// contains filtered or unexported fields
}

Pool wraps a sync.Pool to make it available for any type.

T must be the value type of the pooled object (e.g. Pool[bytes.Buffer]): Pool.Borrow returns a *T. Using a pointer type as T (e.g. Pool[*bytes.Buffer]) would yield a **T and is almost certainly a mistake.

func New

func New[T any]() *Pool[T]

New builds a new Pool to recycle allocations of type T explicitly using Pool.Redeem and the allocated pointer.

Freshly allocated instances of type T are set to their zero value; like recycled instances they are reset (if Resettable) when borrowed, so Pool.Borrow always yields a clean object.

func (*Pool[T]) Borrow

func (p *Pool[T]) Borrow() *T

Borrow an instance from the pool.

If the type implements Resettable, the returned instance is reset before being handed out, so it is always clean.

func (*Pool[T]) Redeem

func (p *Pool[T]) Redeem(ptr *T)

Redeem a borrowed instance to the pool.

A nil pointer is ignored (it would otherwise corrupt the pool: a typed-nil boxed into an interface is not the nil interface that sync.Pool.Put skips).

The instance is reset (if it implements Resettable) before being returned to the pool. After calling Redeem, the caller must drop its reference to ptr: continuing to use it is a use-after-redeem bug.

Unlike PoolRedeemable, this plain pool holds no per-object state, so it cannot detect a double-redeem of the same pointer (which corrupts the pool).

Prefer PoolRedeemable when you want that guard, or the debug build for full tracking.

type PoolRedeemable

type PoolRedeemable[T any] struct {
	// contains filtered or unexported fields
}

PoolRedeemable wraps a sync.Pool to make it available for any type.

It differs from Pool in the way objects are redeemed to the pool: borrowing also yields a cached redeem closure, so no closure is allocated at redeem time.

func NewRedeemable

func NewRedeemable[T any]() *PoolRedeemable[T]

NewRedeemable builds a new redeemable Pool to recycle allocations of type T, and use the inner redeemer to relinquish objects to the pool.

func (*PoolRedeemable[T]) BorrowWithRedeem

func (p *PoolRedeemable[T]) BorrowWithRedeem() (*T, func())

BorrowWithRedeem borrows an instance from the pool and provides the corresponding redeem function.

This is useful for instance to use with defer.

The instance is reset (if it implements Resettable) both when borrowed and when the returned redeem closure is called. After calling the redeem closure, the caller must drop its reference to the returned instance.

Calling the redeem closure more than once panics (see [redeemable.state]): a borrowed instance must be redeemed exactly once.

type PoolSlice

type PoolSlice[T any] struct {
	// contains filtered or unexported fields
}

PoolSlice is a pool of [Slice[T]].

PoolSlice.BorrowWithRedeem will return an empty inner slice by default. This default may be altered using WithMinimumCapacity.

Use PoolSlice.BorrowWithSizeAndRedeem or Slice.Grow to grow the capacity of the inner slice.

func NewPoolSlice

func NewPoolSlice[T any](opts ...PoolSliceOption) *PoolSlice[T]

NewPoolSlice builds a pool to recycle slices of type []T.

func (*PoolSlice[T]) BorrowWithRedeem

func (p *PoolSlice[T]) BorrowWithRedeem() (*Slice[T], func())

BorrowWithRedeem returns the slice wrapper and the redeem closure to relinquish the allocated wrapper.

The wrapper is reset (elements zeroed, length restored) both on borrow and when the redeem closure is called. Calling the redeem closure more than once panics.

func (*PoolSlice[T]) BorrowWithSizeAndRedeem

func (p *PoolSlice[T]) BorrowWithSizeAndRedeem(size int) (*Slice[T], func())

BorrowWithSizeAndRedeem borrows a slice []T from the pool and ensures that its capacity is at least the provided size.

type PoolSliceOption

type PoolSliceOption func(*poolSliceOptions)

PoolSliceOption alters the default settings to allocate new pooled slices

func WithLength

func WithLength(size int) PoolSliceOption

WithLength ensures that the borrowed slices have a fixed given initial length.

By default, the borrowed slices are reset to length 0.

func WithMaxCapacity

func WithMaxCapacity(size int) PoolSliceOption

WithMaxCapacity bounds the capacity of recycled slices.

When a borrowed slice has grown past size at redeem time, its (oversized) backing array is discarded and replaced with a fresh one sized to the minimum capacity, instead of being recycled.

This stops the pool from accumulating large backing arrays after an occasional large request, keeping the steady-state memory bounded.

The trade-off: a workload that genuinely needs slices larger than size will reallocate on every cycle. Set size from the high-water mark you actually expect, not below it. A size of 0 (the default) means no cap: grown slices are recycled as-is.

func WithMinimumCapacity

func WithMinimumCapacity(size int) PoolSliceOption

type Resettable

type Resettable interface {
	Reset()
}

Resettable is an interface for types that want to recycle a clean instance from a Pool.

When T (or rather *T) implements Resettable, the pool calls Reset on an instance both when it is redeemed and when it is borrowed:

  • on redeem, so that no references held by the instance are retained while it sits idle in the pool (which would pin a reference graph alive across a GC cycle);
  • on borrow, so that the next borrower receives a clean object regardless of how the instance reached the pool.

Reset must be safe to call more than once on the same instance (it runs at least twice per cycle).

type Slice

type Slice[T any] struct {
	// contains filtered or unexported fields
}

Slice is a struct that wraps a slice []T.

This is useful to borrow and redeem slices from a pool, without having to constantly manipulate pointers to the slice.

The wrapper holds the authoritative slice header.

Its mutating methods (Slice.Append, Slice.Concat, Slice.Grow) return the current backing slice for convenience, so it reads as an idiomatic []T.

But the returned slice is only a snapshot of the wrapper's state at that moment: if you keep it and grow it yourself with the builtin append and it reallocates, the new backing array lives only in your local copy and is NOT tracked by the wrapper — it will not be recycled when the wrapper is redeemed (and a later borrower would get the old, smaller array).

Rule of thumb: it is fine to read or pass the returned []T to a consumer; but if you plan to grow the slice, keep calling the wrapper's methods so the growth is tracked and recycled.

func (*Slice[T]) Append

func (s *Slice[T]) Append(elems ...T) []T

Append elements to the inner slice and return the current backing slice.

This should be preferred to the append builtin if you plan that the slice will grow and you want the newly allocated space to be tracked and recycled. See Slice for the caveat about growing the returned slice yourself.

func (*Slice[T]) Cap

func (s *Slice[T]) Cap() int

func (*Slice[T]) Clip

func (s *Slice[T]) Clip()

Clip removes unused capacity from the inner slice.

func (*Slice[T]) Concat

func (s *Slice[T]) Concat(slice []T) []T

Concat another slice to the inner slice and return the current backing slice.

Unlike slices.Concat, this reuses the inner slice's capacity instead of always allocating a fresh backing array. See Slice for the caveat about growing the returned slice yourself.

func (*Slice[T]) Grow

func (s *Slice[T]) Grow(size int) []T

Grow the inner slice so it can accommodate at least size more elements without reallocating, and return the current backing slice.

Growth is tracked by the wrapper, so the enlarged backing array is recycled on redeem. See Slice for the caveat about growing the returned slice yourself.

func (*Slice[T]) IndexedElems

func (s *Slice[T]) IndexedElems() iter.Seq2[int, T]

IndexedElems iterates over the inner slice.

func (*Slice[T]) Len

func (s *Slice[T]) Len() int

func (*Slice[T]) Reset

func (s *Slice[T]) Reset()

Reset the inner slice to its configured initial length, keeping allocated capacity.

All elements are zeroed, so the pool never retains stale element references (which would keep a referenced graph alive for slices of pointers) and so a WithLength slice is handed out clean rather than carrying data from a previous borrower.

func (*Slice[T]) Slice

func (s *Slice[T]) Slice() []T

Slice returns the inner slice.

Treat the result as a read-only view (for ranging or passing to a consumer), valid until the next mutation or redeem. To grow or append, use the wrapper methods so the new backing array is tracked and recycled (see Slice).

type TB

type TB interface {
	Helper()
	Errorf(format string, args ...any)
	Logf(format string, args ...any)
}

TB is the subset of testing.TB used by AssertNoLeaks.

It is satisfied by *testing.T and *testing.B.

A local interface is used (rather than importing "testing") so that the release build does not pull the testing package — and its flags — into production binaries.

Directories

Path Synopsis
Package shared provides ready-made, process-wide pools for objects that are commonly recycled across potentially many packages: byte slices, bytes.Buffer and bytes.Reader.
Package shared provides ready-made, process-wide pools for objects that are commonly recycled across potentially many packages: byte slices, bytes.Buffer and bytes.Reader.