From 7c898154b3535f3ed820f6cab3e4c976d92f465c Mon Sep 17 00:00:00 2001 From: root Date: Thu, 5 Feb 2026 02:14:37 +0000 Subject: [PATCH] fix(gost): remove single-node optimization to enable forwarder failover The single-node bypass in hop.Select() was preventing FailFilter from being applied when retry excludes reduced available nodes to one. This caused failed forwarder nodes to keep being selected instead of failing over to healthy alternatives. FailFilter's built-in safety guard (len <= 1 returns as-is) ensures the last remaining node is never permanently blocked. --- go-gost/x/hop/hop.go | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/go-gost/x/hop/hop.go b/go-gost/x/hop/hop.go index f111409..c2b8296 100644 --- a/go-gost/x/hop/hop.go +++ b/go-gost/x/hop/hop.go @@ -204,17 +204,11 @@ func (p *chainHop) Select(ctx context.Context, opts ...hop.SelectOption) *chain. return nodes[0] } - // For single-node case: bypass selector/FailFilter to ensure availability. - // The marker system still works for metrics, but we don't block requests - // based on recent failures - the connection will be attempted regardless. - // This matches upstream go-gost/x behavior. - if len(nodes) == 1 { - return nodes[0] - } - - // Multi-node case: use selector with FailFilter for proper failover. + // Use selector with FailFilter for proper failover. // FailFilter will exclude recently-failed nodes, allowing traffic to // be routed to healthy alternatives. + // Note: FailFilter has a safety guard (len <= 1 returns as-is) to ensure + // the last remaining node is never permanently blocked. if s := p.options.selector; s != nil { log.Debugf("[hop.Select] calling selector.Select with %d nodes", len(nodes)) if node := s.Select(ctx, nodes...); node != nil {