mirror of
https://github.com/luxfi/bft.git
synced 2026-10-30 17:01:54 +00:00
Reapplies the fork's whole delta on top of the merge, as one reviewable diff rather than 43 conflict resolutions. Nothing here changes consensus behaviour. - module github.com/luxfi/bft, Go 1.26.4; root package simplex -> bft, which would otherwise collide with the simplex/ subpackage under this module name. - go.uber.org/zap -> github.com/luxfi/log, so common.Logger takes ...log.Field. zap.Error maps to log.Err: log.Error is luxfi/log's package-level logging function, not a field constructor, and the compiler is the only thing that catches that. Needs luxfi/log v1.6.1 for the Stringers constructor. - avalanchego/ -> host/. The package holds NodeID, VMBlock, Bitmask and size limits — the host node's types, vendored to avoid importing a whole node — so it is named for what it holds. Renaming it luxd/ would have swapped one brand for another and said just as little. - testutil/logger.go reimplemented without zapcore. Upstream layers a keyword-filtering Core, a debug-swallowing Encoder and a Tee because that is zap's extension model; luxfi/log has no such seam, so every policy is applied in one emit. That also fixes two defects the layering caused: SilenceExceptKeywords rebuilt the logger and discarded an installed Intercept hook, and the two sinks read different env vars (LOG_LEVEL vs log_level) so they disagreed about suppressing debug. Hooks run outside the lock, so a hook that logs fails the test instead of deadlocking it. Fatal panics rather than exiting, because exiting takes the suite down with no attribution. - simplex/monitor_test.go keeps a local logger: testutil imports simplex, so an internal test cannot import testutil back without closing a cycle. - README keeps the name Simplex throughout — it is the protocol's own name and the subject of reference [1]. Only the deployment target changed. The HyperSDK sentence still says Avalanche because that sentence is true. - Drops the fork's Makefile: it duplicated unit-tests.sh and fuzz.sh, and ran the suite with -timeout 30s when simplex alone needs over 100s. Co-authored-by: Hanzo Dev <[email protected]>
152 lines
2.9 KiB
Go
152 lines
2.9 KiB
Go
// Copyright (C) 2019-2025, Ava Labs, Inc. All rights reserved.
|
|
// See the file LICENSE for licensing terms.
|
|
|
|
package common
|
|
|
|
import (
|
|
"maps"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/luxfi/log"
|
|
)
|
|
|
|
type timeoutRunner[T comparable] func(ids []T)
|
|
|
|
type TimeoutHandler[T comparable] struct {
|
|
// helpful for logging
|
|
name string
|
|
// how often to run through the tasks
|
|
runInterval time.Duration
|
|
// function to run tasks
|
|
taskRunner timeoutRunner[T]
|
|
lock sync.Mutex
|
|
ticks chan time.Time
|
|
close chan struct{}
|
|
// maps id to a task
|
|
tasks map[T]struct{}
|
|
now time.Time
|
|
|
|
log Logger
|
|
running sync.WaitGroup
|
|
}
|
|
|
|
// NewTimeoutHandler returns a TimeoutHandler and starts a new goroutine that
|
|
// listens for ticks and executes TimeoutTasks.
|
|
func NewTimeoutHandler[T comparable](log Logger, name string, startTime time.Time, runInterval time.Duration, taskRunner timeoutRunner[T]) *TimeoutHandler[T] {
|
|
t := &TimeoutHandler[T]{
|
|
name: name,
|
|
now: startTime,
|
|
tasks: make(map[T]struct{}),
|
|
ticks: make(chan time.Time, 1),
|
|
close: make(chan struct{}),
|
|
runInterval: runInterval,
|
|
taskRunner: taskRunner,
|
|
log: log,
|
|
}
|
|
|
|
t.running.Add(1)
|
|
go t.run(startTime)
|
|
|
|
return t
|
|
}
|
|
|
|
func (t *TimeoutHandler[T]) run(startTime time.Time) {
|
|
defer t.running.Done()
|
|
|
|
lastTickTime := startTime
|
|
|
|
for t.shouldRun() {
|
|
select {
|
|
case now := <-t.ticks:
|
|
if now.Sub(lastTickTime) < t.runInterval {
|
|
continue
|
|
}
|
|
lastTickTime = now
|
|
|
|
// update the current time
|
|
t.lock.Lock()
|
|
t.now = now
|
|
t.lock.Unlock()
|
|
|
|
t.maybeRunTasks()
|
|
case <-t.close:
|
|
return
|
|
}
|
|
}
|
|
}
|
|
|
|
func (t *TimeoutHandler[T]) maybeRunTasks() {
|
|
t.lock.Lock()
|
|
ids := make([]T, 0, len(t.tasks))
|
|
|
|
for id := range t.tasks {
|
|
ids = append(ids, id)
|
|
}
|
|
t.lock.Unlock()
|
|
|
|
if len(ids) == 0 {
|
|
return
|
|
}
|
|
|
|
t.log.Debug("Running task ids", log.Any("task ids", ids), log.String("name", t.name))
|
|
t.taskRunner(ids)
|
|
}
|
|
|
|
func (t *TimeoutHandler[T]) shouldRun() bool {
|
|
select {
|
|
case <-t.close:
|
|
return false
|
|
default:
|
|
return true
|
|
}
|
|
}
|
|
|
|
func (t *TimeoutHandler[T]) Tick(now time.Time) {
|
|
select {
|
|
case t.ticks <- now:
|
|
t.lock.Lock()
|
|
t.now = now
|
|
t.lock.Unlock()
|
|
default:
|
|
t.log.Debug("Dropping tick in timeouthandler", log.String("name", t.name))
|
|
}
|
|
}
|
|
|
|
func (t *TimeoutHandler[T]) AddTask(id T) {
|
|
t.lock.Lock()
|
|
defer t.lock.Unlock()
|
|
|
|
t.tasks[id] = struct{}{}
|
|
t.log.Debug("Adding timeout task", log.Any("id", id), log.String("name", t.name))
|
|
}
|
|
|
|
func (t *TimeoutHandler[T]) RemoveTask(ID T) {
|
|
t.lock.Lock()
|
|
defer t.lock.Unlock()
|
|
|
|
if _, ok := t.tasks[ID]; !ok {
|
|
return
|
|
}
|
|
|
|
t.log.Debug("Removing timeout task", log.Any("id", ID), log.String("name", t.name))
|
|
delete(t.tasks, ID)
|
|
}
|
|
|
|
func (t *TimeoutHandler[T]) RemoveOldTasks(shouldRemove func(id T, _ struct{}) bool) {
|
|
t.lock.Lock()
|
|
defer t.lock.Unlock()
|
|
|
|
maps.DeleteFunc(t.tasks, shouldRemove)
|
|
}
|
|
|
|
func (t *TimeoutHandler[T]) Close() {
|
|
defer t.running.Wait()
|
|
select {
|
|
case <-t.close:
|
|
return
|
|
default:
|
|
close(t.close)
|
|
}
|
|
}
|