X-Git-Url: http://git.giacomolli.uk/onechat/blobdiff_plain/e754189bc2aa32ec43a5dc4ee01d7a6c6e4e388c..d206de3a2e839ffbda9cbdb10a3ed93c4915fce3:/queue.go?ds=sidebyside diff --git a/queue.go b/queue.go index 5f2fcb2..c128d61 100644 --- a/queue.go +++ b/queue.go @@ -1,6 +1,7 @@ package main import ( + "container/heap" "slices" ) @@ -18,18 +19,24 @@ const ( // children are built and their texts do not fit in one node. So the core never // scans the tree for work. // -// A leaf can start when fewer than leafWindow leaves before it are unbuilt. -// Leaves start before merges, oldest first, because a turn waits for the leaves -// of all earlier messages. Merges start in the order in which they became -// ready. A failed build waits until the next message. +// The builds start in the order of the messages: the node whose context ends +// first starts first. The context of leaf i ends before message i, and the +// context of a merge ends after its last message. At a tie the merge starts +// first. So a backlog of leaves does not stop the merges that the view needs to +// shrink. A leaf can start only when fewer than leafWindow leaves before it are +// unbuilt. A failed build waits until the next message. type queue struct { leaves []int - merges []nodeKey + merges mergeHeap queued map[nodeKey]bool running map[nodeKey]bool failed map[nodeKey]bool + retry []nodeKey } +// mergeHeap holds the merges that can start, by the end of their context. +type mergeHeap []nodeKey + func newQueue() *queue { return &queue{ queued: make(map[nodeKey]bool), @@ -51,7 +58,7 @@ func (q *queue) AddMerge(key nodeKey) { } q.queued[key] = true - q.merges = append(q.merges, key) + heap.Push(&q.merges, key) } // Next returns the next node that can start, and marks it as running. @@ -60,17 +67,26 @@ func (q *queue) Next() (nodeKey, bool) { return nodeKey{}, false } - for _, id := range q.leaves[:min(len(q.leaves), leafWindow)] { - key := nodeKey{0, id} - if q.startable(key) { - q.running[key] = true - return key, true - } + leaf, found := q.nextLeaf() + if len(q.merges) > 0 && (!found || nodeEnd(q.merges[0].level, q.merges[0].index) <= leaf.index) { + key := heap.Pop(&q.merges).(nodeKey) + q.running[key] = true + + return key, true + } + + if found { + q.running[leaf] = true } - for _, key := range q.merges { - if q.startable(key) { - q.running[key] = true + return leaf, found +} + +// nextLeaf returns the oldest leaf in the window that can start. +func (q *queue) nextLeaf() (nodeKey, bool) { + for _, id := range q.leaves[:min(len(q.leaves), leafWindow)] { + key := nodeKey{0, id} + if !q.running[key] && !q.failed[key] { return key, true } } @@ -78,39 +94,66 @@ func (q *queue) Next() (nodeKey, bool) { return nodeKey{}, false } -func (q *queue) startable(key nodeKey) bool { - return !q.running[key] && !q.failed[key] -} - // Done removes a built node from the queue. func (q *queue) Done(key nodeKey) { delete(q.running, key) - if key.level == 0 { - q.leaves = slices.DeleteFunc(q.leaves, func(id int) bool { - return id == key.index - }) - + if key.level > 0 { + delete(q.queued, key) return } - delete(q.queued, key) - q.merges = slices.DeleteFunc(q.merges, func(other nodeKey) bool { - return other == key - }) + position := slices.Index(q.leaves, key.index) + if position >= 0 { + q.leaves = slices.Delete(q.leaves, position, position+1) + } } // Fail keeps a node in the queue, but it cannot start until Retry. func (q *queue) Fail(key nodeKey) { delete(q.running, key) + if key.level > 0 { + q.retry = append(q.retry, key) + return + } + q.failed[key] = true } // Retry lets the failed nodes start again. func (q *queue) Retry() { clear(q.failed) + for _, key := range q.retry { + heap.Push(&q.merges, key) + } + + q.retry = q.retry[:0] } // Running returns the number of builds that run. func (q *queue) Running() int { return len(q.running) } + +func (h mergeHeap) Len() int { + return len(h) +} + +func (h mergeHeap) Less(a, b int) bool { + return nodeEnd(h[a].level, h[a].index) < nodeEnd(h[b].level, h[b].index) +} + +func (h mergeHeap) Swap(a, b int) { + h[a], h[b] = h[b], h[a] +} + +func (h *mergeHeap) Push(value any) { + *h = append(*h, value.(nodeKey)) +} + +func (h *mergeHeap) Pop() any { + old := *h + key := old[len(old)-1] + *h = old[:len(old)-1] + + return key +}