Generic, exact-capacity object pool for Go.
Retained TryGet hits use bounded atomic probes without callbacks or allocations. TryPut has the same fast path unless WithHookOnPut is configured. An empty TryGet calls the factory supplied to New.
go get github.com/colduction/boundedpool-go@latestpackage main
import (
"bytes"
"github.com/colduction/boundedpool-go"
)
func main() {
pool, err := boundedpool.New(1024,
func() *bytes.Buffer {
return new(bytes.Buffer)
},
boundedpool.WithHookOnPut(func(buf *bytes.Buffer) {
buf.Reset()
}),
)
if err != nil {
panic(err)
}
buf, err := pool.TryGet()
if err != nil {
return
}
buf.WriteString("hello")
switch err := pool.TryPut(buf); err {
case nil:
// Pool owns buf.
case boundedpool.ErrUnavailable, boundedpool.ErrClosed:
// Caller still owns buf and may dispose or retry it.
}
items, first := pool.Close()
if first {
for _, item := range items {
// Dispose retained items when T owns external resources.
_ = item
}
}
}| Call | Result |
|---|---|
TryGet |
A nil error returns an idle item or a factory-created item. |
TryPut |
A nil error transfers item to pool. Rejection keeps caller ownership. |
Close |
Seals storage and returns every retained item to winning caller. |
Len |
Approximate idle count; zero once Closed reports true. |
Cap |
Exact maximum retained item count. |
Closed |
Whether new operations are rejected. |
New requires a factory that returns a fully initialized, usable, non-nil value. The factory may run concurrently and controls its own blocking and allocations. It must not panic or call methods on the same pool. Items passed to TryPut must also be usable and non-nil.
WithHookOnPut runs a callback after TryPut reserves idle storage and before the item becomes available to TryGet. Use it to reset, clear, or resize pointer-backed values. Rejected puts do not run it, so caller-owned values remain unchanged. The callback may run concurrently and must not panic or call methods on the same pool.
When TryGet wins no retained item during its bounded scan, it calls the factory. Concurrent misses may create more live values than Cap; capacity bounds retained idle values, not checked-out values.
ErrUnavailable is returned only by TryPut when no slot was won. In a quiescent pool this means full; concurrent ownership transfers can make that observation stale immediately. TryPut returns package sentinels directly, so hot callers may compare them with == instead of errors.Is.
Close performs no callbacks. It marks every slot closed, waits for in-progress ownership transfers, put hooks, and factory calls, compacts retained items in the existing backing array, then returns them without allocating. Concurrent losing Close calls wait for drain completion and return nil, false. Checked-out and rejected items remain caller-owned.
An operation overlapping Close may return a transfer that linearized first or ErrClosed; it cannot access returned storage after Close finishes.
| Option | Default | Purpose |
|---|---|---|
WithHookOnPut |
- | Runs a hook on each accepted item before retention. |
WithShardFactor |
2 |
Requests factor × GOMAXPROCS shards. |
WithNumShards |
- | Requests explicit shard count; overrides factor. |
Shard count is normalized to a power of two, capped by capacity, and raised when needed to keep each shard at most 64 slots. Every call probes at most Cap slots. This preserves deterministic capacity and ownership; the scans themselves are O(Cap) and never wait or allocate.
Never use an item after successful TryPut, put one item twice, or copy a pool after first use. Prefer pointers for values containing mutexes, atomics, or other non-copyable state.
sync.Pool may discard cached values at any GC cycle. boundedpool-go retains idle items up to exact configured capacity until Close, then returns them to caller control.
GNU Lesser General Public License v2.1. See LICENSE.