aabebd047b
Replace bare type assertion in ParallelStrategy with comma-ok check to prevent panic on non-Node inputs. Downgrade noisy Infof hash-selection log to Tracef and remove Chinese-language debug output. Add doc comments to all exported symbols in weighted.go.
175 lines
3.6 KiB
Go
175 lines
3.6 KiB
Go
package selector
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"net"
|
|
"sync"
|
|
|
|
"github.com/go-gost/core/chain"
|
|
"github.com/go-gost/core/connector"
|
|
"github.com/go-gost/core/logger"
|
|
"github.com/go-gost/core/selector"
|
|
xnet "github.com/go-gost/x/internal/net"
|
|
)
|
|
|
|
type parallelStrategy[T any] struct{}
|
|
|
|
// ParallelStrategy is a strategy for node selector.
|
|
// It dials all nodes concurrently and uses the first successful connection.
|
|
func ParallelStrategy[T any]() selector.Strategy[T] {
|
|
return ¶llelStrategy[T]{}
|
|
}
|
|
|
|
func (s *parallelStrategy[T]) Apply(ctx context.Context, vs ...T) (v T) {
|
|
if len(vs) == 0 {
|
|
return
|
|
}
|
|
if len(vs) == 1 {
|
|
return vs[0]
|
|
}
|
|
|
|
nodes := make([]*chain.Node, 0, len(vs))
|
|
for _, node := range vs {
|
|
n, ok := any(node).(*chain.Node)
|
|
if !ok {
|
|
return
|
|
}
|
|
nodes = append(nodes, n)
|
|
}
|
|
|
|
vn := chain.NewNode("parallel", "", chain.TransportNodeOption(¶llelTransporter{nodes: nodes}))
|
|
return any(vn).(T)
|
|
}
|
|
|
|
type parallelConn struct {
|
|
net.Conn
|
|
node *chain.Node
|
|
}
|
|
|
|
type parallelTransporter struct {
|
|
nodes []*chain.Node
|
|
}
|
|
|
|
func (tr *parallelTransporter) Dial(ctx context.Context, addr string) (net.Conn, error) {
|
|
if len(tr.nodes) == 0 {
|
|
return nil, errors.New("no nodes available")
|
|
}
|
|
|
|
ctx, cancel := context.WithCancel(ctx)
|
|
|
|
type result struct {
|
|
conn net.Conn
|
|
node *chain.Node
|
|
err error
|
|
}
|
|
|
|
resCh := make(chan result, len(tr.nodes))
|
|
|
|
var wg sync.WaitGroup
|
|
for _, node := range tr.nodes {
|
|
wg.Add(1)
|
|
go func(n *chain.Node) {
|
|
defer wg.Done()
|
|
|
|
raddr, err := xnet.Resolve(ctx, "ip", n.Addr, n.Options().Resolver, n.Options().HostMapper, logger.Default())
|
|
if err != nil {
|
|
resCh <- result{err: err, node: n}
|
|
return
|
|
}
|
|
|
|
cc, err := n.Options().Transport.Dial(ctx, raddr)
|
|
resCh <- result{conn: cc, node: n, err: err}
|
|
}(node)
|
|
}
|
|
|
|
var best result
|
|
var errs []error
|
|
|
|
for i := 0; i < len(tr.nodes); i++ {
|
|
res := <-resCh
|
|
if res.err != nil {
|
|
errs = append(errs, res.err)
|
|
continue
|
|
}
|
|
|
|
best = res
|
|
break
|
|
}
|
|
|
|
cancel()
|
|
|
|
if best.conn == nil {
|
|
go func() {
|
|
wg.Wait()
|
|
close(resCh)
|
|
}()
|
|
return nil, errors.Join(errs...)
|
|
}
|
|
|
|
go func() {
|
|
// Drain the rest of the channel to clean up late successful connections
|
|
for i := len(errs) + 1; i < len(tr.nodes); i++ {
|
|
res := <-resCh
|
|
if res.err == nil && res.conn != nil {
|
|
res.conn.Close()
|
|
}
|
|
}
|
|
wg.Wait()
|
|
close(resCh)
|
|
}()
|
|
|
|
return ¶llelConn{
|
|
Conn: best.conn,
|
|
node: best.node,
|
|
}, nil
|
|
}
|
|
|
|
func (tr *parallelTransporter) Handshake(ctx context.Context, conn net.Conn) (net.Conn, error) {
|
|
pc, ok := conn.(*parallelConn)
|
|
if !ok {
|
|
return conn, nil
|
|
}
|
|
|
|
cc, err := pc.node.Options().Transport.Handshake(ctx, pc.Conn)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
pc.Conn = cc
|
|
|
|
return pc, nil
|
|
}
|
|
|
|
func (tr *parallelTransporter) Connect(ctx context.Context, conn net.Conn, network, address string) (net.Conn, error) {
|
|
pc, ok := conn.(*parallelConn)
|
|
if !ok {
|
|
return nil, errors.New("invalid connection type")
|
|
}
|
|
|
|
return pc.node.Options().Transport.Connect(ctx, pc.Conn, network, address)
|
|
}
|
|
|
|
func (tr *parallelTransporter) Bind(ctx context.Context, conn net.Conn, network, address string, opts ...connector.BindOption) (net.Listener, error) {
|
|
pc, ok := conn.(*parallelConn)
|
|
if !ok {
|
|
return nil, errors.New("invalid connection type")
|
|
}
|
|
|
|
return pc.node.Options().Transport.Bind(ctx, pc.Conn, network, address, opts...)
|
|
}
|
|
|
|
func (tr *parallelTransporter) Multiplex() bool {
|
|
return false
|
|
}
|
|
|
|
func (tr *parallelTransporter) Options() *chain.TransportOptions {
|
|
return &chain.TransportOptions{}
|
|
}
|
|
|
|
// Copy implements chain.Transporter.Copy.
|
|
func (tr *parallelTransporter) Copy() chain.Transporter {
|
|
return ¶llelTransporter{
|
|
nodes: append([]*chain.Node(nil), tr.nodes...),
|
|
}
|
|
}
|