pool.go 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213
  1. // Copyright (c) Tailscale Inc & AUTHORS
  2. // SPDX-License-Identifier: BSD-3-Clause
  3. // Package pool contains a generic type for managing a pool of resources; for
  4. // example, connections to a database, or to a remote service.
  5. //
  6. // Unlike sync.Pool from the Go standard library, this pool does not remove
  7. // items from the pool when garbage collection happens, nor is it safe for
  8. // concurrent use like sync.Pool.
  9. package pool
  10. import (
  11. "fmt"
  12. "math/rand/v2"
  13. "tailscale.com/types/ptr"
  14. )
  15. // consistencyCheck enables additional runtime checks to ensure that the pool
  16. // is well-formed; it is disabled by default, and can be enabled during tests
  17. // to catch additional bugs.
  18. const consistencyCheck = false
  19. // Pool is a pool of resources. It is not safe for concurrent use.
  20. type Pool[V any] struct {
  21. s []itemAndIndex[V]
  22. }
  23. type itemAndIndex[V any] struct {
  24. // item is the element in the pool
  25. item V
  26. // index is the current location of this item in pool.s. It gets set to
  27. // -1 when the item is removed from the pool.
  28. index *int
  29. }
  30. // Handle is an opaque handle to a resource in a pool. It is used to delete an
  31. // item from the pool, without requiring the item to be comparable.
  32. type Handle[V any] struct {
  33. idx *int // pointer to index; -1 if not in slice
  34. }
  35. // Len returns the current size of the pool.
  36. func (p *Pool[V]) Len() int {
  37. return len(p.s)
  38. }
  39. // Clear removes all items from the pool.
  40. func (p *Pool[V]) Clear() {
  41. p.s = nil
  42. }
  43. // AppendTakeAll removes all items from the pool, appending them to the
  44. // provided slice (which can be nil) and returning them. The returned slice can
  45. // be nil if the provided slice was nil and the pool was empty.
  46. //
  47. // This function does not free the backing storage for the pool; to do that,
  48. // use the Clear function.
  49. func (p *Pool[V]) AppendTakeAll(dst []V) []V {
  50. ret := dst
  51. for i := range p.s {
  52. e := p.s[i]
  53. if consistencyCheck && e.index == nil {
  54. panic(fmt.Sprintf("pool: index is nil at %d", i))
  55. }
  56. if *e.index >= 0 {
  57. ret = append(ret, p.s[i].item)
  58. }
  59. }
  60. p.s = p.s[:0]
  61. return ret
  62. }
  63. // Add adds an item to the pool and returns a handle to it. The handle can be
  64. // used to delete the item from the pool with the Delete method.
  65. func (p *Pool[V]) Add(item V) Handle[V] {
  66. // Store the index in a pointer, so that we can pass it to both the
  67. // handle and store it in the itemAndIndex.
  68. idx := ptr.To(len(p.s))
  69. p.s = append(p.s, itemAndIndex[V]{
  70. item: item,
  71. index: idx,
  72. })
  73. return Handle[V]{idx}
  74. }
  75. // Peek will return the item with the given handle without removing it from the
  76. // pool.
  77. //
  78. // It will return ok=false if the item has been deleted or previously taken.
  79. func (p *Pool[V]) Peek(h Handle[V]) (v V, ok bool) {
  80. p.checkHandle(h)
  81. idx := *h.idx
  82. if idx < 0 {
  83. var zero V
  84. return zero, false
  85. }
  86. p.checkIndex(idx)
  87. return p.s[idx].item, true
  88. }
  89. // Delete removes the item from the pool.
  90. //
  91. // It reports whether the element was deleted; it will return false if the item
  92. // has been taken with the TakeRandom function, or if the item was already
  93. // deleted.
  94. func (p *Pool[V]) Delete(h Handle[V]) bool {
  95. p.checkHandle(h)
  96. idx := *h.idx
  97. if idx < 0 {
  98. return false
  99. }
  100. p.deleteIndex(idx)
  101. return true
  102. }
  103. func (p *Pool[V]) deleteIndex(idx int) {
  104. // Mark the item as deleted.
  105. p.checkIndex(idx)
  106. *(p.s[idx].index) = -1
  107. // If this isn't the last element in the slice, overwrite the element
  108. // at this item's index with the last element.
  109. lastIdx := len(p.s) - 1
  110. if idx < lastIdx {
  111. last := p.s[lastIdx]
  112. p.checkElem(lastIdx, last)
  113. *last.index = idx
  114. p.s[idx] = last
  115. }
  116. // Zero out last element (for GC) and truncate slice.
  117. p.s[lastIdx] = itemAndIndex[V]{}
  118. p.s = p.s[:lastIdx]
  119. }
  120. // Take will remove the item with the given handle from the pool and return it.
  121. //
  122. // It will return ok=false and the zero value if the item has been deleted or
  123. // previously taken.
  124. func (p *Pool[V]) Take(h Handle[V]) (v V, ok bool) {
  125. p.checkHandle(h)
  126. idx := *h.idx
  127. if idx < 0 {
  128. var zero V
  129. return zero, false
  130. }
  131. e := p.s[idx]
  132. p.deleteIndex(idx)
  133. return e.item, true
  134. }
  135. // TakeRandom returns and removes a random element from p
  136. // and reports whether there was one to take.
  137. //
  138. // It will return ok=false and the zero value if the pool is empty.
  139. func (p *Pool[V]) TakeRandom() (v V, ok bool) {
  140. if len(p.s) == 0 {
  141. var zero V
  142. return zero, false
  143. }
  144. pick := rand.IntN(len(p.s))
  145. e := p.s[pick]
  146. p.checkElem(pick, e)
  147. p.deleteIndex(pick)
  148. return e.item, true
  149. }
  150. // checkIndex verifies that the provided index is within the bounds of the
  151. // pool's slice, and that the corresponding element has a non-nil index
  152. // pointer, and panics if not.
  153. func (p *Pool[V]) checkIndex(idx int) {
  154. if !consistencyCheck {
  155. return
  156. }
  157. if idx >= len(p.s) {
  158. panic(fmt.Sprintf("pool: index %d out of range (len %d)", idx, len(p.s)))
  159. }
  160. if p.s[idx].index == nil {
  161. panic(fmt.Sprintf("pool: index is nil at %d", idx))
  162. }
  163. }
  164. // checkHandle verifies that the provided handle is not nil, and panics if it
  165. // is.
  166. func (p *Pool[V]) checkHandle(h Handle[V]) {
  167. if !consistencyCheck {
  168. return
  169. }
  170. if h.idx == nil {
  171. panic("pool: nil handle")
  172. }
  173. }
  174. // checkElem verifies that the provided itemAndIndex has a non-nil index, and
  175. // that the stored index matches the expected position within the slice.
  176. func (p *Pool[V]) checkElem(idx int, e itemAndIndex[V]) {
  177. if !consistencyCheck {
  178. return
  179. }
  180. if e.index == nil {
  181. panic("pool: index is nil")
  182. }
  183. if got := *e.index; got != idx {
  184. panic(fmt.Sprintf("pool: index is incorrect: want %d, got %d", idx, got))
  185. }
  186. }