wait

package module
v0.0.0-...-cc31886 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 31, 2026 License: MIT Imports: 5 Imported by: 1

README

wait

Go Reference

A waitlist for pooling reusable resources.

What

wait.List manages a pool of items where waiters are served in FIFO order and item creation is lazy up to a configurable limit.

Why

Sometimes you need to bound how many of something you create—database connections, file handles, expensive objects. And sometimes fairness matters: you don't want request 10,000 to starve while requests 10,001-11,000 get serviced.

A buffered channel can pool things, but can't guarantee fairness or control creation. sync.Pool reduces allocation overhead but doesn't bound creation or provide ordering. wait.List trades some throughput for predictable latency and bounded resource use.

When

Use this when:

  • Resources are expensive to create
  • Fairness matters
  • You need a hard cap on instances

Don't use this when:

  • A buffered channel is sufficient
  • You don't care about fairness
  • Maximum throughput is the only goal

How

pool := &wait.List[*sql.Conn]{
    MaxItems:   10,  // never create more than 10 connections
    MaxWaiters: 100, // reject requests if queue is too long
    New: func() *sql.Conn {
        // only called if we haven't hit MaxItems
        return openConnection()
    },
}

conn, err := pool.Take(ctx)
if err != nil {
    return err
}
defer pool.Put(conn)

// use conn

Call Put to return a reusable checked-out item. Call Retire instead when the checked-out item should never be returned and the pool should eventually replace it.

See package documentation for details.

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

View Source
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

func (l *Line[D]) TryWait(d D) bool

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

func (l *Line[D]) Wait(ctx context.Context, d D) error

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

func (p *List[T]) Put(v T) (accepted bool)

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

func (p *List[T]) Take(ctx context.Context) (T, error)

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.

func (*List[T]) TryTake

func (p *List[T]) TryTake() (_ T, ok bool)

TryTake returns the next ready item without blocking and ok=true; otherwise, it returns a zero value and ok=false. Unlike [Take], it never waits and never spawns New goroutines.

Directories

Path Synopsis
Package queue implements FIFO and LIFO queues.
Package queue implements FIFO and LIFO queues.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL