Documentation
¶
Overview ¶
Package wait provides a waitlist for pooling reusable resources.
A List manages a pool of items with two key properties: waiters are served in FIFO order, and item creation is lazy up to a configurable limit. This makes it suitable for expensive resources like database connections where fairness matters and you want to avoid creating more than necessary.
Unlike sync.Pool, which is designed for reducing allocation overhead of temporary objects, List bounds resource creation and guarantees FIFO fairness.
List trades some throughput for predictable latency. If you don't need fairness or creation limits, a buffered channel is simpler. Checked-out items return to the pool with List.Put or permanently leave it with List.Retire.
A Line pools nothing: it admits demands in strict first-come order to capacity the caller accounts for, blocking each demand until it fits.
Index ¶
Constants ¶
This section is empty.
Variables ¶
var ( // ErrMaxWaiters is returned by [List.Take] when MaxWaiters is exceeded. ErrMaxWaiters = errors.New("too many waiters") // ErrClosed is returned by [List.Take] and [Line.Wait] when the // List or Line is closed. ErrClosed = errors.New("closed") )
Functions ¶
This section is empty.
Types ¶
type Line ¶
type Line[D any] struct { // Fill reports whether d can be admitted now, deducting whatever // d needs from the caller's accounting when it returns true. // The Line calls Fill with its lock held, only ever for the // demand at the head of the line (or for a lone Wait or TryWait // caller when the line is empty), so the accounting needs no // lock of its own. Fill must not block or call back into the // Line. A nil Fill admits everything. Fill func(d D) bool // Refill returns d's capacity to the caller's accounting. // Put calls it with the Line's lock held, before offering the // head of the line to Fill again. A nil Refill is a no-op. Refill func(d D) // contains filtered or unexported fields }
A Line admits demands in first-come order.
Wait joins the line with a demand and blocks until the demand is admitted, ctx is done, or the Line is closed. Only the demand at the head of the line is ever offered to Fill; when Fill accepts, that waiter is admitted and the next head is offered. A demand behind the head is never admitted first, no matter how small — strict arrival order, intentional head-of-line blocking.
Unlike List, a Line carries no items. It orders admission to capacity the caller accounts for in Fill and Refill.
The zero value is a usable Line that admits everything. It is safe for concurrent use.
func (*Line[D]) Close ¶
func (l *Line[D]) Close()
Close closes the Line. Waiting goroutines are unblocked and receive ErrClosed; nothing is refunded, because a demand still in line never deducted anything. Wait and TryWait fail after Close. Close is idempotent.
func (*Line[D]) Put ¶
func (l *Line[D]) Put(d D)
Put returns d's capacity to the caller's accounting via Refill, then admits from the head of the line for as long as Fill accepts — one Put may admit several waiters. Put never blocks.
Put works after Close: the refill still lands, since the accounting belongs to the caller; there is just no one left to admit.
func (*Line[D]) TryWait ¶
TryWait admits d without waiting if the line is empty and Fill accepts it, and reports whether d was admitted. A TryWait caller never takes capacity ahead of anyone already in line. TryWait returns false after Close.
func (*Line[D]) Wait ¶
Wait joins the line with demand d and blocks until d is admitted, ctx is done, or the Line is closed.
If the line is empty and Fill accepts d, Wait admits immediately without queueing. Wait returns nil once admitted, ErrClosed if the Line is closed, and the context cause if ctx is done first — even when an admission raced the cancellation: the raced grant is refunded via Refill, so a non-nil error means d holds nothing.
type List ¶
type List[Item any] struct { // MaxItems is the maximum number of items to create via New. // Zero means no limit. MaxItems int // New creates an item when the ready queue is empty and MaxItems allows. // If nil, New returns the zero value of Item. New func() Item // MaxWaiters is the maximum number of goroutines that can wait. // Take returns ErrMaxWaiters when this limit is reached. // Zero means no limit. MaxWaiters int // contains filtered or unexported fields }
List is a waitlist for pooling items of type Item.
Waiters are served in FIFO order. When no waiters are present, ready items are stored in a LIFO stack. When the ready queue is empty and MaxItems allows, Take spawns a goroutine to create a new item with List.New.
The zero value is a usable List with no limits. It is safe for concurrent use.
func (*List[T]) Close ¶
func (p *List[T]) Close()
Close closes the List. Waiting goroutines are unblocked and will receive ErrClosed. Ready items can still be drained via Take or TryTake. Future Put calls return false. Close is idempotent.
func (*List[T]) Put ¶
Put adds v to the List. If waiters exist, v is handed to the longest-waiting goroutine in FIFO order. Otherwise, v is added to the ready stack in LIFO order.
Put returns false if the List is closed, true otherwise. Put does not block.
func (*List[T]) Retire ¶
func (p *List[T]) Retire()
Retire permanently removes one checked-out item from the live item count.
If the List is open and there is a waiting goroutine, Retire starts exactly one replacement load using List.New. If there are no live items to retire, Retire is a no-op.
func (*List[T]) Take ¶
Take returns an item from the List, blocking until one is available, ctx is done, or the List is closed.
If a ready item exists, Take returns it immediately regardless of ctx or close state. Otherwise, if MaxItems has not been reached, Take spawns a goroutine to call New (or a function returning the zero value if New is nil) and waits in FIFO order for a result.
Take returns ErrMaxWaiters if the waiter limit is reached. Take returns ErrClosed when closed with no ready items remaining. Take returns the context error if ctx is canceled before receiving an item.