go / pool

I use a handle-based pool to manage resources that need specific removal, unlike sync.Pool which can evict items at any GC cycle.

The problem

sync.Pool is designed for temporary object reuse:

var bufPool = sync.Pool{
    New: func() any { return new(bytes.Buffer) },
}

buf := bufPool.Get().(*bytes.Buffer)
defer bufPool.Put(buf)

But sync.Pool has limitations:

For connection pools or worker tracking, you need items to persist and be individually removable.

The pattern

package pool

type Pool[V any] struct {
	s []itemAndIndex[V]
}

type itemAndIndex[V any] struct {
	item  V
	index *int
}

type Handle[V any] struct {
	idx *int
}

func (p *Pool[V]) Add(item V) Handle[V] {
	idx := new(int)
	*idx = len(p.s)
	p.s = append(p.s, itemAndIndex[V]{item: item, index: idx})
	return Handle[V]{idx: idx}
}

func (p *Pool[V]) Delete(h Handle[V]) bool {
	idx := *h.idx
	if idx < 0 {
		return false
	}
	*h.idx = -1 // mark deleted

	// Swap with last element for O(1) removal
	last := len(p.s) - 1
	if idx < last {
		p.s[idx] = p.s[last]
		*p.s[idx].index = idx
	}
	p.s = p.s[:last]
	return true
}

func (p *Pool[V]) TakeRandom() (V, bool) {
	if len(p.s) == 0 {
		var zero V
		return zero, false
	}
	pick := rand.IntN(len(p.s))
	h := Handle[V]{idx: p.s[pick].index}
	item := p.s[pick].item
	p.Delete(h)
	return item, true
}

Key points:

Usage

Track active connections:

var connections pool.Pool[*Conn]

func onConnect(conn *Conn) Handle[*Conn] {
    return connections.Add(conn)
}

func onDisconnect(h Handle[*Conn]) {
    connections.Delete(h)
}

Load balance across workers:

var workers pool.Pool[*Worker]

func dispatch(job Job) bool {
    worker, ok := workers.TakeRandom()
    if !ok {
        return false // no workers available
    }
    worker.Send(job)
    return true
}

When to use

When not to use

← All articles