-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathreaper.go
More file actions
152 lines (133 loc) · 4.13 KB
/
Copy pathreaper.go
File metadata and controls
152 lines (133 loc) · 4.13 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
// Package reaper implements a deterministic, bounded rewrite loop over a
// priority structure.
//
// A Reaper repeatedly removes a single element from a heap, allows a callback
// to inspect it and optionally emit replacement elements, and then reinserts
// those elements back into the heap. Each step is strictly bounded: a callback
// may emit at most a fixed number of elements ("degree") per visit.
//
// The design enforces three core invariants:
//
// 1. Only one element is popped from the heap at a time.
// 2. Reinsertion is explicitly controlled and bounded.
// 3. Callbacks are invoked without holding heap locks.
//
// This makes Reaper suitable for schedulers, rewrite systems, planners,
// or any algorithm that requires controlled feedback into a priority queue.
package reaper
import (
"errors"
"sync"
)
// Heap is a thread-safe priority structure supporting push and pop operations.
//
// Reaper assumes that all heap mutations are protected by the provided Lock
// and Unlock methods. Callbacks are always invoked without holding the heap lock.
type Heap[T any] interface {
Push(elem T)
Pop() T
Empty() bool
Lock()
Unlock()
}
// Callback is invoked for each element popped from the heap during a Reap
// operation.
//
// The callback receives the popped element ("front") and an emit function.
// The emit function may be used to buffer elements for reinsertion into the
// heap after the callback returns.
//
// Returning stop == true signals that reaping should terminate early.
// In that case, the popped element is reinserted into the heap unchanged,
// and no buffered emissions are committed.
type Callback[T any] interface {
Visit(front T, emit func(...T) error) (stop bool)
}
// CallbackFunc allows a function to be used as a Callback.
type CallbackFunc[T any] func(front T, emit func(...T) error) (stop bool)
func (f CallbackFunc[T]) Visit(front T, emit func(...T) error) (stop bool) {
return f(front, emit)
}
// Reaper administrates the controlled feedback loop over a Heap.
type Reaper[T any] struct {
degree int
pool sync.Pool
}
// New creates a new Reaper with the specified degree.
//
// Degree specifies the maximum number of elements that can be emitted per
// Visit call. A degree of zero disables emission entirely, turning the Reaper
// into a pure consumer.
func New[T any](degree int) *Reaper[T] {
return &Reaper[T]{
degree: degree,
pool: sync.Pool{
New: func() any {
s := make([]T, 0, degree)
return &s
},
},
}
}
// ErrEmitBufferOverflow is returned by the emit function when the number of
// emitted elements in a single Visit exceeds the configured degree.
var ErrEmitBufferOverflow = errors.New("emit buffer overflow")
// Result indicates the outcome of a Reap operation.
//
//go:generate stringer -type=Result
type Result int
const (
// Exhausted indicates that the heap became empty and no further
// elements were available for processing.
Exhausted Result = iota
// Stopped indicates that the callback requested early termination.
// In this case, the last popped element was reinserted into the heap.
Stopped
)
// Reap executes the rewrite loop over the provided heap using the given
// callback.
//
// Reap repeatedly pops a single element from the heap and invokes the callback
// on it. If the callback emits elements, they are reinserted atomically after
// the callback returns. If the callback requests terminations, the popped
// element is restored, the emitted elments are discarded, and Reap returns
// Stopped.
//
// Reap returns Exhausted if the heap becomes empty.
func (r *Reaper[T]) Reap(h Heap[T], cb Callback[T]) Result {
buf := r.pool.Get().(*[]T)
defer func() {
*buf = (*buf)[:0]
r.pool.Put(buf)
}()
emit := func(elems ...T) error {
if len(*buf)+len(elems) > r.degree {
return ErrEmitBufferOverflow
}
*buf = append(*buf, elems...)
return nil
}
for {
var front T
h.Lock()
if h.Empty() {
h.Unlock()
return Exhausted
}
front = h.Pop()
h.Unlock()
stop := cb.Visit(front, emit)
if stop {
h.Lock()
h.Push(front)
h.Unlock()
return Stopped
}
h.Lock()
for _, elem := range *buf {
h.Push(elem)
}
h.Unlock()
*buf = (*buf)[:0]
}
}