| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213 |
- // Copyright (c) Tailscale Inc & AUTHORS
- // SPDX-License-Identifier: BSD-3-Clause
- // Package pool contains a generic type for managing a pool of resources; for
- // example, connections to a database, or to a remote service.
- //
- // Unlike sync.Pool from the Go standard library, this pool does not remove
- // items from the pool when garbage collection happens, nor is it safe for
- // concurrent use like sync.Pool.
- package pool
- import (
- "fmt"
- "math/rand/v2"
- "tailscale.com/types/ptr"
- )
- // consistencyCheck enables additional runtime checks to ensure that the pool
- // is well-formed; it is disabled by default, and can be enabled during tests
- // to catch additional bugs.
- const consistencyCheck = false
- // Pool is a pool of resources. It is not safe for concurrent use.
- type Pool[V any] struct {
- s []itemAndIndex[V]
- }
- type itemAndIndex[V any] struct {
- // item is the element in the pool
- item V
- // index is the current location of this item in pool.s. It gets set to
- // -1 when the item is removed from the pool.
- index *int
- }
- // Handle is an opaque handle to a resource in a pool. It is used to delete an
- // item from the pool, without requiring the item to be comparable.
- type Handle[V any] struct {
- idx *int // pointer to index; -1 if not in slice
- }
- // Len returns the current size of the pool.
- func (p *Pool[V]) Len() int {
- return len(p.s)
- }
- // Clear removes all items from the pool.
- func (p *Pool[V]) Clear() {
- p.s = nil
- }
- // AppendTakeAll removes all items from the pool, appending them to the
- // provided slice (which can be nil) and returning them. The returned slice can
- // be nil if the provided slice was nil and the pool was empty.
- //
- // This function does not free the backing storage for the pool; to do that,
- // use the Clear function.
- func (p *Pool[V]) AppendTakeAll(dst []V) []V {
- ret := dst
- for i := range p.s {
- e := p.s[i]
- if consistencyCheck && e.index == nil {
- panic(fmt.Sprintf("pool: index is nil at %d", i))
- }
- if *e.index >= 0 {
- ret = append(ret, p.s[i].item)
- }
- }
- p.s = p.s[:0]
- return ret
- }
- // Add adds an item to the pool and returns a handle to it. The handle can be
- // used to delete the item from the pool with the Delete method.
- func (p *Pool[V]) Add(item V) Handle[V] {
- // Store the index in a pointer, so that we can pass it to both the
- // handle and store it in the itemAndIndex.
- idx := ptr.To(len(p.s))
- p.s = append(p.s, itemAndIndex[V]{
- item: item,
- index: idx,
- })
- return Handle[V]{idx}
- }
- // Peek will return the item with the given handle without removing it from the
- // pool.
- //
- // It will return ok=false if the item has been deleted or previously taken.
- func (p *Pool[V]) Peek(h Handle[V]) (v V, ok bool) {
- p.checkHandle(h)
- idx := *h.idx
- if idx < 0 {
- var zero V
- return zero, false
- }
- p.checkIndex(idx)
- return p.s[idx].item, true
- }
- // Delete removes the item from the pool.
- //
- // It reports whether the element was deleted; it will return false if the item
- // has been taken with the TakeRandom function, or if the item was already
- // deleted.
- func (p *Pool[V]) Delete(h Handle[V]) bool {
- p.checkHandle(h)
- idx := *h.idx
- if idx < 0 {
- return false
- }
- p.deleteIndex(idx)
- return true
- }
- func (p *Pool[V]) deleteIndex(idx int) {
- // Mark the item as deleted.
- p.checkIndex(idx)
- *(p.s[idx].index) = -1
- // If this isn't the last element in the slice, overwrite the element
- // at this item's index with the last element.
- lastIdx := len(p.s) - 1
- if idx < lastIdx {
- last := p.s[lastIdx]
- p.checkElem(lastIdx, last)
- *last.index = idx
- p.s[idx] = last
- }
- // Zero out last element (for GC) and truncate slice.
- p.s[lastIdx] = itemAndIndex[V]{}
- p.s = p.s[:lastIdx]
- }
- // Take will remove the item with the given handle from the pool and return it.
- //
- // It will return ok=false and the zero value if the item has been deleted or
- // previously taken.
- func (p *Pool[V]) Take(h Handle[V]) (v V, ok bool) {
- p.checkHandle(h)
- idx := *h.idx
- if idx < 0 {
- var zero V
- return zero, false
- }
- e := p.s[idx]
- p.deleteIndex(idx)
- return e.item, true
- }
- // TakeRandom returns and removes a random element from p
- // and reports whether there was one to take.
- //
- // It will return ok=false and the zero value if the pool is empty.
- func (p *Pool[V]) TakeRandom() (v V, ok bool) {
- if len(p.s) == 0 {
- var zero V
- return zero, false
- }
- pick := rand.IntN(len(p.s))
- e := p.s[pick]
- p.checkElem(pick, e)
- p.deleteIndex(pick)
- return e.item, true
- }
- // checkIndex verifies that the provided index is within the bounds of the
- // pool's slice, and that the corresponding element has a non-nil index
- // pointer, and panics if not.
- func (p *Pool[V]) checkIndex(idx int) {
- if !consistencyCheck {
- return
- }
- if idx >= len(p.s) {
- panic(fmt.Sprintf("pool: index %d out of range (len %d)", idx, len(p.s)))
- }
- if p.s[idx].index == nil {
- panic(fmt.Sprintf("pool: index is nil at %d", idx))
- }
- }
- // checkHandle verifies that the provided handle is not nil, and panics if it
- // is.
- func (p *Pool[V]) checkHandle(h Handle[V]) {
- if !consistencyCheck {
- return
- }
- if h.idx == nil {
- panic("pool: nil handle")
- }
- }
- // checkElem verifies that the provided itemAndIndex has a non-nil index, and
- // that the stored index matches the expected position within the slice.
- func (p *Pool[V]) checkElem(idx int, e itemAndIndex[V]) {
- if !consistencyCheck {
- return
- }
- if e.index == nil {
- panic("pool: index is nil")
- }
- if got := *e.index; got != idx {
- panic(fmt.Sprintf("pool: index is incorrect: want %d, got %d", idx, got))
- }
- }
|