github.com/MetalBlockchain/metalgo@v1.11.9/x/sync/workheap.go (about) 1 // Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. 2 // See the file LICENSE for licensing terms. 3 4 package sync 5 6 import ( 7 "bytes" 8 9 "github.com/google/btree" 10 11 "github.com/MetalBlockchain/metalgo/utils/heap" 12 "github.com/MetalBlockchain/metalgo/utils/maybe" 13 ) 14 15 // A priority queue of syncWorkItems. 16 // Note that work item ranges never overlap. 17 // Supports range merging and priority updating. 18 // Not safe for concurrent use. 19 type workHeap struct { 20 // Max heap of items by priority. 21 // i.e. heap.Pop returns highest priority item. 22 innerHeap heap.Set[*workItem] 23 // The heap items sorted by range start. 24 // A Nothing start is considered to be the smallest. 25 sortedItems *btree.BTreeG[*workItem] 26 closed bool 27 } 28 29 func newWorkHeap() *workHeap { 30 return &workHeap{ 31 innerHeap: heap.NewSet[*workItem](func(a, b *workItem) bool { 32 return a.priority > b.priority 33 }), 34 sortedItems: btree.NewG( 35 2, 36 func(a, b *workItem) bool { 37 aNothing := a.start.IsNothing() 38 bNothing := b.start.IsNothing() 39 if aNothing { 40 // [a] is Nothing, so if [b] is Nothing, they're equal. 41 // Otherwise, [b] is greater. 42 return !bNothing 43 } 44 if bNothing { 45 // [a] has a value and [b] doesn't so [a] is greater. 46 return false 47 } 48 // [a] and [b] both contain values. Compare the values. 49 return bytes.Compare(a.start.Value(), b.start.Value()) < 0 50 }, 51 ), 52 } 53 } 54 55 // Marks the heap as closed. 56 func (wh *workHeap) Close() { 57 wh.closed = true 58 } 59 60 // Adds a new [item] into the heap. Will not merge items, unlike MergeInsert. 61 func (wh *workHeap) Insert(item *workItem) { 62 if wh.closed { 63 return 64 } 65 66 wh.innerHeap.Push(item) 67 wh.sortedItems.ReplaceOrInsert(item) 68 } 69 70 // Pops and returns a work item from the heap. 71 // Returns nil if no work is available or the heap is closed. 72 func (wh *workHeap) GetWork() *workItem { 73 if wh.closed || wh.Len() == 0 { 74 return nil 75 } 76 item, _ := wh.innerHeap.Pop() 77 wh.sortedItems.Delete(item) 78 return item 79 } 80 81 // Insert the item into the heap, merging it with existing items 82 // that share a boundary and root ID. 83 // e.g. if the heap contains a work item with range 84 // [0,10] and then [10,20] is inserted, we will merge the two 85 // into a single work item with range [0,20]. 86 // e.g. if the heap contains work items [0,10] and [20,30], 87 // and we add [10,20], we will merge them into [0,30]. 88 func (wh *workHeap) MergeInsert(item *workItem) { 89 if wh.closed { 90 return 91 } 92 93 var mergedBefore, mergedAfter *workItem 94 searchItem := &workItem{ 95 start: item.start, 96 } 97 98 // Find the item with the greatest start range which is less than [item.start]. 99 // Note that the iterator function will run at most once, since it always returns false. 100 wh.sortedItems.DescendLessOrEqual( 101 searchItem, 102 func(beforeItem *workItem) bool { 103 if item.localRootID == beforeItem.localRootID && 104 maybe.Equal(item.start, beforeItem.end, bytes.Equal) { 105 // [beforeItem.start, beforeItem.end] and [item.start, item.end] are 106 // merged into [beforeItem.start, item.end] 107 beforeItem.end = item.end 108 beforeItem.priority = max(item.priority, beforeItem.priority) 109 wh.innerHeap.Fix(beforeItem) 110 mergedBefore = beforeItem 111 } 112 return false 113 }) 114 115 // Find the item with the smallest start range which is greater than [item.start]. 116 // Note that the iterator function will run at most once, since it always returns false. 117 wh.sortedItems.AscendGreaterOrEqual( 118 searchItem, 119 func(afterItem *workItem) bool { 120 if item.localRootID == afterItem.localRootID && 121 maybe.Equal(item.end, afterItem.start, bytes.Equal) { 122 // [item.start, item.end] and [afterItem.start, afterItem.end] are merged into 123 // [item.start, afterItem.end]. 124 afterItem.start = item.start 125 afterItem.priority = max(item.priority, afterItem.priority) 126 wh.innerHeap.Fix(afterItem) 127 mergedAfter = afterItem 128 } 129 return false 130 }) 131 132 // if the new item should be merged with both the item before and the item after, 133 // we can combine the before item with the after item 134 if mergedBefore != nil && mergedAfter != nil { 135 // combine the two ranges 136 mergedBefore.end = mergedAfter.end 137 // remove the second range since it is now covered by the first 138 wh.remove(mergedAfter) 139 // update the priority 140 mergedBefore.priority = max(mergedBefore.priority, mergedAfter.priority) 141 wh.innerHeap.Fix(mergedBefore) 142 } 143 144 // nothing was merged, so add new item to the heap 145 if mergedBefore == nil && mergedAfter == nil { 146 // We didn't merge [item] with an existing one; put it in the heap. 147 wh.Insert(item) 148 } 149 } 150 151 // Deletes [item] from the heap. 152 func (wh *workHeap) remove(item *workItem) { 153 wh.innerHeap.Remove(item) 154 wh.sortedItems.Delete(item) 155 } 156 157 func (wh *workHeap) Len() int { 158 return wh.innerHeap.Len() 159 }