|
| 1 | +# reaper |
| 2 | + |
| 3 | +`reaper` implements a deterministic, bounded rewrite loop over a |
| 4 | +priority structure. |
| 5 | + |
| 6 | +It repeatedly removes a single elment from a heap, allows controlled |
| 7 | +reinsertion, and enforces a strict upper bound on growth per step. |
| 8 | + |
| 9 | +## Motivation |
| 10 | + |
| 11 | +Many systems require repeatedly rewriting or rescheduling items based |
| 12 | +on partial information. `reaper` addresses this need while enforcing |
| 13 | +three invariants: |
| 14 | + |
| 15 | +1. Only one element is processed at a time |
| 16 | +2. Reinsertion is explicitly bounded |
| 17 | +3. Callbacks run without holding heap locks |
| 18 | + |
| 19 | +## Core Concepts |
| 20 | + |
| 21 | +### Heap |
| 22 | + |
| 23 | +A `Heap` is a priority structure protected by explicit locking. Reaper |
| 24 | +does not assume ownership of synchronization, allowing it to be |
| 25 | +integrated into exisiting concurrent systems. |
| 26 | + |
| 27 | +### Callback |
| 28 | + |
| 29 | +A `Callback` inspects a popped element and may emit a replacements. |
| 30 | + |
| 31 | +```go |
| 32 | +Visit(front T, emit func(...T) error) (stop bool) |
| 33 | +``` |
| 34 | + |
| 35 | +- `emit` buffers elements for reinsertion. It returns an error if the |
| 36 | + buffer is full. |
| 37 | +- emission is bounded by the configured degree |
| 38 | +- returning `true` stops the reaping process and restores `front` to |
| 39 | + the heap without reinsertion |
| 40 | + |
| 41 | +### Degree |
| 42 | + |
| 43 | +The degree defines the maximum number of elements a callback may |
| 44 | +emit for reinsertion per visit. |
| 45 | + |
| 46 | +- `degree > 0`: bounded rewrite system (DAG) |
| 47 | +- `degree == 0`: pure consumer |
| 48 | + |
| 49 | +## Usage |
| 50 | + |
| 51 | +```go |
| 52 | +r := reaper.New[int](2) // degree 2 |
| 53 | + |
| 54 | +cb := reaper.CallbackFunc[int](func(front int, emit func(...int) error) bool { |
| 55 | + if front == 1 { |
| 56 | + emit(4, 5) |
| 57 | + } |
| 58 | + |
| 59 | + retur false |
| 60 | +}) |
| 61 | + |
| 62 | +res := r.Reap(heap, cb) |
| 63 | +``` |
| 64 | + |
| 65 | +### Results |
| 66 | +`Reap` returns one of: |
| 67 | + |
| 68 | +- `Exhausted`: the heap became empty |
| 69 | +- `Stopped`: the callback requested termination |
| 70 | + |
| 71 | +Both of which are perfectly normal outcomes. |
| 72 | + |
| 73 | +## Guarantees |
| 74 | + |
| 75 | +- No heap mutation occurs while callbacks exectue, except caused by |
| 76 | + another goroutine |
| 77 | +- No callback can cause unbounded growth |
| 78 | +- The heap is always in a valid state |
| 79 | + |
| 80 | +## Heap Adapter |
| 81 | + |
| 82 | +The `heapadapter` subpackage provides adapters for using Go’s `container/heap` |
| 83 | +with `reaper`. |
| 84 | + |
| 85 | +```go |
| 86 | +h := &MyHeap{} // implements container/heap.Interface of Item |
| 87 | +mu := &sync.Mutex{} |
| 88 | + |
| 89 | +heap := heapadapter.New[Item](h, mu) |
| 90 | +r := reaper.New(0) |
| 91 | +_ = r.Reap(heap, cb) |
| 92 | +``` |
0 commit comments