Skip to content
JohanLindvallPublic

About

Typed object pools for Go: generic sync.Pool, free lists that survive garbage collection, and buffer trimming policies.

Topics

Resources

Code of conduct

Contributing

Security policy

Stars

1 star

Watchers

0 watching

Forks

Repository files navigation

objpool

Type-safe object reuse for Go, with control over what survives garbage collection.

CI Go Reference Go 1.18+ MIT License

Reuse buffers, encoders, and scratch objects without repeating type assertions or reset logic at every call site. objpool provides three small building blocks:

  • Pool[T] wraps sync.Pool. Get returns a ready *T, never nil.
  • FreeList[T] keeps idle objects across garbage collections, with an optional limit on how many it retains.
  • RatchetTrim helps you retire objects whose buffers stayed oversized after a burst of traffic.

Both pools support concurrent callers. The library uses only the Go standard library at runtime.

Quick start · Choosing a pool · Usage guide · Benchmarks · Contributing

Quick start

Requires Go 1.18 or later. Inside your Go module:

go get github.com/JohanLindvall/objpool

Save this as main.go, then run go run .:

package main

import (
	"bytes"
	"fmt"

	"github.com/JohanLindvall/objpool"
)

func main() {
	buffers := objpool.New(
		func() *bytes.Buffer { return new(bytes.Buffer) },
		func(b *bytes.Buffer) error { b.Reset(); return nil },
	)

	buf := buffers.Get()
	defer buffers.Put(buf)

	buf.WriteString("hello, objpool")
	fmt.Println(buf.String())
}

Output:

hello, objpool

Create the pool once and share it across operations. Each operation gets an object, uses it, then returns it exactly once. Recycled objects are reset by Get, so every caller receives a ready object.

The executable examples also cover reuse across garbage collections, compression writers, and trimming. Run them from a checkout with go test -run Example -v ./....

Choosing a pool

Need Use Tradeoff
Reuse temporary objects that are cheap to rebuild Pool[T] The runtime may discard idle objects at any time.
Keep expensive objects warm across garbage collections FreeList[T] Idle objects retain memory until reused or the list becomes unreachable.
Drop buffers that stay oversized after a burst Either pool + RatchetTrim Your release code decides whether to return the object.

Pool[T] has the same retention contract as sync.Pool. Its purpose is typed access and centralized initialization and reset.

FreeList[T] uses a mutex-protected stack and returns the most recently returned object. Set maxIdle to limit idle object count; it does not limit concurrent checkouts or total memory:

buffers := objpool.NewFreeList(64,
	func() *bytes.Buffer { return bytes.NewBuffer(make([]byte, 0, 1<<20)) },
	func(b *bytes.Buffer) error { b.Reset(); return nil },
)

Here, up to 64 idle buffers keep their capacity across garbage collections. maxIdle <= 0 means no idle limit. A buffer can grow beyond its initial 1 MiB; see the memory and trimming guide.

Profile before adding a pool: allocating a small, short-lived value may already be inexpensive. These are reuse pools for in-memory objects; they do not provide connection management, cleanup callbacks, or a concurrency limit.

The lifecycle in five rules

  1. Build ready objects. newFn must return a ready, non-nil *T. A nil constructor function panics immediately; a nil result panics during Get.
  2. Reset on reuse. resetFn runs during Get only for recycled objects. Pass nil if no reset is needed. A reset error discards the object and builds a replacement; the error is not returned to the caller.
  3. Own one checkout at a time. Stop using an object and anything that aliases its storage after Put. Return each checkout at most once. Put(nil) is safe.
  4. Keep callbacks safe for concurrent calls. Pools synchronize storage; callbacks run on the calling goroutine and can run concurrently for different objects. The objects themselves are not made safe for concurrent use.
  5. Use the constructors. Pool zero values are not usable. Do not copy a pool after first use.

Put does not clear contents. If data must be cleared immediately, do that before returning the object. The usage guide explains ownership, reset errors, caller-supplied reset arguments, and memory retention.

API at a glance

Symbol Purpose
New[T](newFn, resetFn) Create a typed Pool[T].
NewFreeList[T](maxIdle, newFn, resetFn) Create a FreeList[T] with an optional idle limit.
Get() *T / Put(*T) Check out a ready object / return it for reuse.
DefaultRatchetTrim() Create a policy with a 16 MiB floor, factor 4, and 8 strikes.
(*RatchetTrim).Due(retained, used) Record one use cycle and decide whether to drop the object.

See Go Reference for full signatures, contracts, and examples.

Performance

The repository includes allocation, sync.Pool, Pool, and FreeList benchmarks with both serial and parallel workloads:

go test -run '^$' -bench . -benchmem ./...

Use the benchmark guide to reproduce results and compare changes. Throughput and memory savings depend on object size, concurrency, and garbage collection frequency.

Help and contributing

Questions, bug reports, documentation fixes, and realistic benchmark cases are welcome. Open an issue or read the contribution guide to get started.

Please follow the code of conduct and use the security policy for private vulnerability reports.

Maintained by Johan Lindvall. If objpool helps your project, a star helps other Go developers discover it.

Versions and license

Version tags are available to Go modules. CI tags each passing commit on main with the next patch version; minor and major versions are chosen by the maintainer. During v0, APIs may change between releases, so review changes when updating.

Released under the MIT license.

About

Typed object pools for Go: generic sync.Pool, free lists that survive garbage collection, and buffer trimming policies.

Topics

Resources

Code of conduct

Contributing

Security policy

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages