DEV Community

Adrian Brad
Adrian Brad

Posted on

Go queue v1.7.0: a new Delay queue for timers, retries, and TTLs

I just tagged v1.7.0 of github.com/adrianbrad/queue, a thread-safe generic queue package. The headline feature is a new Delay queue.

What is Delay?

Delay is a priority queue where each element becomes dequeuable at a deadline computed by a caller-supplied function at Offer time. Get returns ErrNoElementsAvailable until the head is due; GetWait sleeps until it is.

type task struct {
    id    int
    runAt time.Time
}

delayQueue := queue.NewDelay(
    []task{
        {id: 1, runAt: time.Now().Add(20 * time.Millisecond)},
        {id: 2, runAt: time.Now().Add(5 * time.Millisecond)},
    },
    func(t task) time.Time { return t.runAt },
)

// Blocks until the earliest-deadline element is due.
next := delayQueue.GetWait()
fmt.Println(next.id) // 2
Enter fullscreen mode Exit fullscreen mode

Useful for retry scheduling, TTL expiry, timer wheels, and similar patterns where work should become visible at a future wall-clock time.

Implementation notes

  • Min-heap by deadline, written directly on a typed slice instead of via container/heap. pprof showed container/heap's any-boxing accounted for most of the allocations, so steady-state offer/get is now zero-alloc.
  • GetWait uses sync.Cond + time.AfterFunc so "head is due" and "queue state changed" compose under a single Wait.
  • Peek returns the head regardless of whether its deadline has passed (matches java.util.concurrent.DelayQueue.peek).

Five queues, one interface

Delay joins Blocking, Priority, Circular, and Linked. All satisfy the same Queue[T comparable] interface, so you can swap implementations without changing call sites. The README has a small decision table for picking one.

Bonus: v1.6.0 shipped a lot

Since the last post I also tagged v1.6.0, a heavy batch of correctness and performance work:

  • Priority.MarshalJSON was emitting elements in arbitrary heap-array order; now emits in priority order.
  • Reset on Blocking, Circular, and Priority now honours capacity and zeros dropped slots (pointer T was leaking).
  • Blocking switched from Signal to Broadcast, removing a fragile cascade in PeekWait.
  • Linked grew an internal node free list; steady-state offer/get dropped from 16 B / 1 alloc to 0.
  • Capacity validation at construction time (panics with a clear message instead of a cryptic slice panic later).

Feedback welcome

Thanks for reading.

Top comments (0)