Reactor is a sharded object pool for high-concurrency workloads. It asks a narrow question: when the allocator and GC become the bottleneck on a hot path, what does pooling actually buy, and what does it cost?
An object pool is often treated as a synonym for "performance optimization", but it is really a trade: memory retention and lifecycle management in exchange for fewer allocations and steadier tail latency. This project keeps both sides of that trade in the design.
Why a pool
In workloads with high concurrency and short-lived objects, allocation rate turns directly into GC pressure and p99 jitter. The standard library's sync.Pool is excellent for temporary buffers, but it offers no capacity limit, lifecycle hooks, or controllable reclamation policy.
Reactor targets a different kind of object: expensive to initialize, safe to reset, explicitly borrowed and returned, and visible when the pool is under pressure.
Structure
- each P or worker keeps a local free list to reduce lock contention;
- when the local list is empty, the pool steals from another shard;
- if nothing is available, it allocates a new object up to a hard limit;
- returned objects are reset before going back to the local shard;
- objects above the idle limit are dropped instead of retained forever.
Every pool implements four lifecycle hooks:
type Pool[T any] interface {
New() T
Reset(T)
Validate(T) bool
Destroy(T)
}
Reset clears state, Validate decides whether an object is still reusable, and Destroy releases connections, buffers, and other explicit resources.
Lifecycle and safety
The hardest part of pooling is not concurrency. It is returning an object while someone still holds a reference. In debug mode, Reactor records borrow and return sites and provides basic leak detection.
The pool is bounded. Once the limit is reached, a borrow can wait for a short window or fail immediately, handing pressure back to the caller. That choice is explicit configuration, never a silent block.
Trade-offs and observability
Pooling is not free:
- retaining objects means higher resident memory;
- an incomplete reset contaminates the next borrower;
- too many shards make stealing expensive; too few recreate contention;
- for short-lived objects, pooling can be slower than allocation.
Reactor therefore reports hit rate, allocation count, borrow wait time, failed returns, and p50/p99 latency in the same place. Without that data, there is no reason to assume a pool is faster.
Current state
The borrow/return core, sharded free lists, metrics, and benchmark harness are taking shape. Next are leak detection, adaptive capacity, and a head-to-head load test against sync.Pool.
A pool is a latency tool, not a memory optimization. If you cannot measure it, do not pool it.