mirror of
https://github.com/Sagit-chu/flvx.git
synced 2026-10-12 04:06:37 +08:00
fix(agent): retire replaced tunnel sessions (#541)
This commit is contained in:
@@ -2,6 +2,8 @@ package chain
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"io"
|
||||
|
||||
"github.com/go-gost/core/chain"
|
||||
"github.com/go-gost/core/hop"
|
||||
@@ -38,11 +40,12 @@ type chainNamer interface {
|
||||
}
|
||||
|
||||
type Chain struct {
|
||||
name string
|
||||
hops []hop.Hop
|
||||
marker selector.Marker
|
||||
metadata metadata.Metadata
|
||||
logger logger.Logger
|
||||
name string
|
||||
hops []hop.Hop
|
||||
ownedHops []hop.Hop
|
||||
marker selector.Marker
|
||||
metadata metadata.Metadata
|
||||
logger logger.Logger
|
||||
}
|
||||
|
||||
func NewChain(name string, opts ...ChainOption) *Chain {
|
||||
@@ -61,8 +64,15 @@ func NewChain(name string, opts ...ChainOption) *Chain {
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Chain) AddHop(hop hop.Hop) {
|
||||
func (c *Chain) AddHop(hop hop.Hop, owned ...bool) {
|
||||
c.hops = append(c.hops, hop)
|
||||
isOwned := true
|
||||
if len(owned) > 0 {
|
||||
isOwned = owned[0]
|
||||
}
|
||||
if isOwned {
|
||||
c.ownedHops = append(c.ownedHops, hop)
|
||||
}
|
||||
}
|
||||
|
||||
// Metadata implements metadata.Metadatable interface.
|
||||
@@ -112,6 +122,36 @@ func (c *Chain) Route(ctx context.Context, network, address string, opts ...chai
|
||||
return rt
|
||||
}
|
||||
|
||||
// Retire gracefully drains resources owned by a chain that has been replaced.
|
||||
func (c *Chain) Retire() {
|
||||
if c == nil {
|
||||
return
|
||||
}
|
||||
for _, h := range c.ownedHops {
|
||||
if retirer, ok := h.(interface{ Retire() }); ok {
|
||||
retirer.Retire()
|
||||
continue
|
||||
}
|
||||
if closer, ok := h.(io.Closer); ok {
|
||||
_ = closer.Close()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Close immediately releases all resources owned by the chain.
|
||||
func (c *Chain) Close() error {
|
||||
if c == nil {
|
||||
return nil
|
||||
}
|
||||
var errs []error
|
||||
for _, h := range c.ownedHops {
|
||||
if closer, ok := h.(io.Closer); ok {
|
||||
errs = append(errs, closer.Close())
|
||||
}
|
||||
}
|
||||
return errors.Join(errs...)
|
||||
}
|
||||
|
||||
type chainGroup struct {
|
||||
chains []chain.Chainer
|
||||
selector selector.Selector[chain.Chainer]
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
package chain
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
corechain "github.com/go-gost/core/chain"
|
||||
corehop "github.com/go-gost/core/hop"
|
||||
)
|
||||
|
||||
type lifecycleTestHop struct {
|
||||
selected int
|
||||
retired int
|
||||
closed int
|
||||
}
|
||||
|
||||
func (h *lifecycleTestHop) Select(context.Context, ...corehop.SelectOption) *corechain.Node {
|
||||
h.selected++
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *lifecycleTestHop) Retire() {
|
||||
h.retired++
|
||||
}
|
||||
|
||||
func (h *lifecycleTestHop) Close() error {
|
||||
h.closed++
|
||||
return nil
|
||||
}
|
||||
|
||||
func TestChainRoutesThroughSharedHopWithoutOwningLifecycle(t *testing.T) {
|
||||
hop := &lifecycleTestHop{}
|
||||
chain := NewChain("shared-hop")
|
||||
chain.AddHop(hop, false)
|
||||
|
||||
if route := chain.Route(context.Background(), "tcp", "example.com:443"); route == nil {
|
||||
t.Fatal("route is nil")
|
||||
}
|
||||
if hop.selected != 1 {
|
||||
t.Fatalf("shared hop selected %d times, want 1", hop.selected)
|
||||
}
|
||||
|
||||
chain.Retire()
|
||||
if err := chain.Close(); err != nil {
|
||||
t.Fatalf("close chain: %v", err)
|
||||
}
|
||||
if hop.retired != 0 || hop.closed != 0 {
|
||||
t.Fatalf("shared hop lifecycle changed: retired=%d closed=%d", hop.retired, hop.closed)
|
||||
}
|
||||
}
|
||||
|
||||
func TestChainRetiresAndClosesOwnedHop(t *testing.T) {
|
||||
hop := &lifecycleTestHop{}
|
||||
chain := NewChain("owned-hop")
|
||||
chain.AddHop(hop)
|
||||
|
||||
chain.Retire()
|
||||
if err := chain.Close(); err != nil {
|
||||
t.Fatalf("close chain: %v", err)
|
||||
}
|
||||
if hop.retired != 1 || hop.closed != 1 {
|
||||
t.Fatalf("owned hop lifecycle: retired=%d closed=%d, want 1/1", hop.retired, hop.closed)
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,8 @@ package chain
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"io"
|
||||
"net"
|
||||
|
||||
"github.com/go-gost/core/chain"
|
||||
@@ -102,5 +104,34 @@ func (tr *Transport) Options() *chain.TransportOptions {
|
||||
func (tr *Transport) Copy() chain.Transporter {
|
||||
tr2 := &Transport{}
|
||||
*tr2 = *tr
|
||||
return tr
|
||||
return tr2
|
||||
}
|
||||
|
||||
// Retire prevents long-lived dialer sessions owned by an obsolete chain from
|
||||
// accepting new streams while allowing existing streams to drain.
|
||||
func (tr *Transport) Retire() {
|
||||
if tr == nil {
|
||||
return
|
||||
}
|
||||
if retirer, ok := tr.dialer.(interface{ Retire() }); ok {
|
||||
retirer.Retire()
|
||||
}
|
||||
if retirer, ok := tr.connector.(interface{ Retire() }); ok {
|
||||
retirer.Retire()
|
||||
}
|
||||
}
|
||||
|
||||
// Close immediately releases transport-owned dialer and connector resources.
|
||||
func (tr *Transport) Close() error {
|
||||
if tr == nil {
|
||||
return nil
|
||||
}
|
||||
var errs []error
|
||||
if closer, ok := tr.dialer.(io.Closer); ok {
|
||||
errs = append(errs, closer.Close())
|
||||
}
|
||||
if closer, ok := tr.connector.(io.Closer); ok {
|
||||
errs = append(errs, closer.Close())
|
||||
}
|
||||
return errors.Join(errs...)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
package chain
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net"
|
||||
"testing"
|
||||
|
||||
corechain "github.com/go-gost/core/chain"
|
||||
)
|
||||
|
||||
type copyTestRoute struct{}
|
||||
|
||||
func (copyTestRoute) Dial(context.Context, string, string, ...corechain.DialOption) (net.Conn, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (copyTestRoute) Bind(context.Context, string, string, ...corechain.BindOption) (net.Listener, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (copyTestRoute) Nodes() []*corechain.Node {
|
||||
return nil
|
||||
}
|
||||
|
||||
func TestTransportCopyReturnsIndependentTransport(t *testing.T) {
|
||||
originalRoute := copyTestRoute{}
|
||||
replacementRoute := ©TestRoute{}
|
||||
original := NewTransport(nil, nil, corechain.RouteTransportOption(originalRoute))
|
||||
|
||||
copied, ok := original.Copy().(*Transport)
|
||||
if !ok {
|
||||
t.Fatalf("copy type = %T, want *Transport", original.Copy())
|
||||
}
|
||||
if copied == original {
|
||||
t.Fatal("Copy returned the original transport")
|
||||
}
|
||||
|
||||
copied.Options().Route = replacementRoute
|
||||
if original.Options().Route != originalRoute {
|
||||
t.Fatal("mutating copied transport changed original route")
|
||||
}
|
||||
if copied.Options().Route != replacementRoute {
|
||||
t.Fatal("copied transport did not retain its independent route")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user