// Package sample provides a bounded queue that keeps a random sample of recent // items instead of a FIFO. A FIFO fills with the first N items and drops the // rest, which starves later work; random replacement gives representative // coverage at bounded memory. Not for hot paths. package sample import ( mathrand "math/rand/v2" "sync" ) type Queue[T any] struct { mu sync.Mutex items []T cap int notify chan struct{} stop chan struct{} once sync.Once } func New[T any](capacity int) *Queue[T] { if capacity < 1 { capacity = 1 } return &Queue[T]{ cap: capacity, notify: make(chan struct{}, 1), stop: make(chan struct{}), } } func (q *Queue[T]) Push(v T) { q.mu.Lock() if len(q.items) < q.cap { q.items = append(q.items, v) } else { q.items[mathrand.IntN(len(q.items))] = v } q.mu.Unlock() select { case q.notify <- struct{}{}: default: } } func (q *Queue[T]) Pop() (T, bool) { q.mu.Lock() n := len(q.items) if n == 0 { q.mu.Unlock() var zero T return zero, false } i := mathrand.IntN(n) v := q.items[i] q.items[i] = q.items[n-1] q.items = q.items[:n-1] q.mu.Unlock() return v, true } func (q *Queue[T]) Len() int { q.mu.Lock() defer q.mu.Unlock() return len(q.items) } func (q *Queue[T]) Notify() <-chan struct{} { return q.notify } func (q *Queue[T]) Stop() <-chan struct{} { return q.stop } func (q *Queue[T]) Close() { q.once.Do(func() { close(q.stop) }) select { case q.notify <- struct{}{}: default: } }