reaper implements a deterministic, bounded rewrite loop over a
priority structure.
It repeatedly removes a single elment from a heap, allows controlled reinsertion, and enforces a strict upper bound on growth per step.
Many systems require repeatedly rewriting or rescheduling items based
on partial information. reaper addresses this need while enforcing
three invariants:
- Only one element is processed at a time
- Reinsertion is explicitly bounded
- Callbacks run without holding heap locks
A Heap is a priority structure protected by explicit locking. Reaper
does not assume ownership of synchronization, allowing it to be
integrated into exisiting concurrent systems.
A Callback inspects a popped element and may emit a replacements.
Visit(front T, emit func(...T) error) (stop bool)emitbuffers elements for reinsertion. It returns an error if the buffer is full.- emission is bounded by the configured degree
- returning
truestops the reaping process and restoresfrontto the heap without reinsertion
The degree defines the maximum number of elements a callback may emit for reinsertion per visit.
degree > 0: bounded rewrite system (DAG)degree == 0: pure consumer
r := reaper.New[int](2) // degree 2
cb := reaper.CallbackFunc[int](func(front int, emit func(...int) error) bool {
if front == 1 {
emit(4, 5)
}
retur false
})
res := r.Reap(heap, cb)Reap returns one of:
Exhausted: the heap became emptyStopped: the callback requested termination
Both of which are perfectly normal outcomes.
- No heap mutation occurs while callbacks exectue, except caused by another goroutine
- No callback can cause unbounded growth
- The heap is always in a valid state
The heapadapter subpackage provides adapters for using Go’s container/heap
with reaper.
h := &MyHeap{} // implements container/heap.Interface of Item
mu := &sync.Mutex{}
heap := heapadapter.New[Item](h, mu)
r := reaper.New(0)
_ = r.Reap(heap, cb)