Files
gost-x/selector/latency.go
T
ginuerzh 46fc054ad5 x: implement node-level liveness probe
- x/chain/node.go: StartNodeProbe goroutine — periodic TCP/HTTP probing
- x/internal/probe/probe.go: HTTPProber with status code validation
- x/selector/latency.go: LatencyFilter, LowestLatencyStrategy (generic)
- x/config: ProbeConfig, MaxLatency, parseProbeConfig, lowestlatency strategy
- x/hop/hop.go: Close() cascade to node.Close()
- x/chain/chain.go: Chain.Close() cascade to hop.Close()

Tests: 4 latency selector tests, 4 node probe tests, 4 HTTP prober tests,
1 hop failover integration test.
2026-07-16 20:27:21 +08:00

86 lines
1.8 KiB
Go

package selector
import (
"context"
"time"
"github.com/go-gost/core/chain"
"github.com/go-gost/core/selector"
)
type latencyFilter[T any] struct {
maxLatency time.Duration
}
// LatencyFilter filters out items whose probe result indicates failure or
// latency exceeding maxLatency. Items without a probe result are passed through
// (no instrumentation = no filtering).
func LatencyFilter[T any](maxLatency time.Duration) selector.Filter[T] {
return &latencyFilter[T]{maxLatency: maxLatency}
}
func (f *latencyFilter[T]) Filter(ctx context.Context, vs ...T) []T {
if len(vs) <= 1 || f.maxLatency <= 0 {
return vs
}
var out []T
for _, v := range vs {
if reader, ok := any(v).(chain.ProbeResultReader); ok {
r := reader.ProbeResult()
if r != nil && (!r.Success || r.Latency > f.maxLatency) {
continue
}
}
out = append(out, v)
}
return out
}
type lowestLatencyStrategy[T any] struct{}
// LowestLatencyStrategy selects the item with the lowest probe latency.
// Items without a probe result or with failed probes are deprioritized
// (placed after items with successful probes).
func LowestLatencyStrategy[T any]() selector.Strategy[T] {
return &lowestLatencyStrategy[T]{}
}
func (s *lowestLatencyStrategy[T]) Apply(ctx context.Context, vs ...T) (v T) {
if len(vs) == 0 {
return
}
var bestLatency time.Duration
var bestIdx int = -1
var fallback int = -1
for i, item := range vs {
reader, ok := any(item).(chain.ProbeResultReader)
if !ok {
if fallback < 0 {
fallback = i
}
continue
}
r := reader.ProbeResult()
if r == nil || !r.Success {
if fallback < 0 {
fallback = i
}
continue
}
if bestIdx < 0 || r.Latency < bestLatency {
bestLatency = r.Latency
bestIdx = i
}
}
if bestIdx >= 0 {
return vs[bestIdx]
}
if fallback >= 0 {
return vs[fallback]
}
return vs[0]
}