mirror of
https://github.com/hanzoai/traces.git
synced 2026-10-30 18:20:41 +00:00
132 lines
2.9 KiB
Go
132 lines
2.9 KiB
Go
package flushqueues
|
|
|
|
import (
|
|
"container/heap"
|
|
"errors"
|
|
"sync"
|
|
|
|
"github.com/prometheus/client_golang/prometheus"
|
|
)
|
|
|
|
// PriorityQueue is a priority queue.
|
|
type PriorityQueue[T Op] struct {
|
|
lock sync.Mutex
|
|
cond *sync.Cond
|
|
closing bool
|
|
closed bool
|
|
hit map[string]struct{}
|
|
queue queue[T]
|
|
lengthGauge prometheus.Gauge
|
|
}
|
|
|
|
// Op is an operation on the priority queue.
|
|
type Op interface {
|
|
Key() string
|
|
Priority() int64 // The larger the number the higher the priority.
|
|
}
|
|
|
|
type queue[T Op] []T
|
|
|
|
func (q queue[T]) Len() int { return len(q) }
|
|
func (q queue[T]) Less(i, j int) bool { return q[i].Priority() > q[j].Priority() }
|
|
func (q queue[T]) Swap(i, j int) { q[i], q[j] = q[j], q[i] }
|
|
|
|
// Push and Pop use pointer receivers because they modify the slice's length,
|
|
// not just its contents.
|
|
func (q *queue[T]) Push(x interface{}) {
|
|
*q = append(*q, x.(T))
|
|
}
|
|
|
|
func (q *queue[T]) Pop() interface{} {
|
|
old := *q
|
|
n := len(old)
|
|
x := old[n-1]
|
|
*q = old[0 : n-1]
|
|
return x
|
|
}
|
|
|
|
// NewPriorityQueue makes a new priority queue.
|
|
func NewPriorityQueue[T Op](lengthGauge prometheus.Gauge) *PriorityQueue[T] {
|
|
pq := &PriorityQueue[T]{
|
|
hit: map[string]struct{}{},
|
|
lengthGauge: lengthGauge,
|
|
}
|
|
pq.cond = sync.NewCond(&pq.lock)
|
|
heap.Init(&pq.queue)
|
|
return pq
|
|
}
|
|
|
|
// Length returns the length of the queue.
|
|
func (pq *PriorityQueue[T]) Length() int {
|
|
pq.lock.Lock()
|
|
defer pq.lock.Unlock()
|
|
return len(pq.queue)
|
|
}
|
|
|
|
// Close signals that the queue should be closed when it is empty.
|
|
// A closed queue will not accept new items.
|
|
func (pq *PriorityQueue[T]) Close() {
|
|
pq.lock.Lock()
|
|
defer pq.lock.Unlock()
|
|
pq.closing = true
|
|
pq.cond.Broadcast()
|
|
}
|
|
|
|
// DiscardAndClose closes the queue and removes all the items from it.
|
|
func (pq *PriorityQueue[T]) DiscardAndClose() {
|
|
pq.lock.Lock()
|
|
defer pq.lock.Unlock()
|
|
pq.closed = true
|
|
pq.queue = nil
|
|
pq.hit = map[string]struct{}{}
|
|
pq.cond.Broadcast()
|
|
}
|
|
|
|
// Enqueue adds an operation to the queue in priority order. Returns
|
|
// true if added; false if the operation was already on the queue.
|
|
func (pq *PriorityQueue[T]) Enqueue(op T) (bool, error) {
|
|
pq.lock.Lock()
|
|
defer pq.lock.Unlock()
|
|
|
|
if pq.closed {
|
|
return false, errors.New("enqueue on closed queue")
|
|
}
|
|
|
|
_, enqueued := pq.hit[op.Key()]
|
|
if enqueued {
|
|
return false, nil
|
|
}
|
|
|
|
pq.hit[op.Key()] = struct{}{}
|
|
heap.Push(&pq.queue, op)
|
|
pq.cond.Broadcast()
|
|
if pq.lengthGauge != nil {
|
|
pq.lengthGauge.Inc()
|
|
}
|
|
return true, nil
|
|
}
|
|
|
|
// Dequeue will return the op with the highest priority; block if queue is
|
|
// empty; returns the zero value of T if queue is closed.
|
|
func (pq *PriorityQueue[T]) Dequeue() T {
|
|
pq.lock.Lock()
|
|
defer pq.lock.Unlock()
|
|
|
|
for len(pq.queue) == 0 && !pq.closing && !pq.closed {
|
|
pq.cond.Wait()
|
|
}
|
|
|
|
if len(pq.queue) == 0 && (pq.closing || pq.closed) {
|
|
pq.closed = true
|
|
var zero T
|
|
return zero
|
|
}
|
|
|
|
op := heap.Pop(&pq.queue).(T)
|
|
delete(pq.hit, op.Key())
|
|
if pq.lengthGauge != nil {
|
|
pq.lengthGauge.Dec()
|
|
}
|
|
return op
|
|
}
|