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  }