package client import ( "context" "net" "sync" "testing" "time" "github.com/fatedier/frp/client/proxy" "github.com/fatedier/frp/client/visitor" v1 "github.com/fatedier/frp/pkg/config/v1" "github.com/fatedier/frp/pkg/msg" ) type gracefulCloseTestConnector struct { conn net.Conn } func (*gracefulCloseTestConnector) Connect() (*msg.Conn, error) { return nil, net.ErrClosed } func (c *gracefulCloseTestConnector) Close() error { return c.conn.Close() } func newGracefulCloseTestService() *Service { ctx := context.Background() common := &v1.ClientCommonConfig{} serverConn, clientConn := net.Pipe() ctl := &Control{ ctx: ctx, sessionCtx: &SessionContext{ Common: common, RunID: "graceful-close-race", Conn: msg.NewConn(clientConn, msg.NewV1ReadWriter(clientConn)), Connector: &gracefulCloseTestConnector{conn: serverConn}, }, doneCh: make(chan struct{}), } ctl.pm = proxy.NewManager(ctx, common, nil, nil, nil) ctl.vm = visitor.NewManager(ctx, "graceful-close-race", common, nil, nil, nil) return &Service{ctl: ctl, cancel: context.CancelCauseFunc(func(error) {})} } func TestGracefulCloseAndStopSynchronizeDuration(t *testing.T) { for i := range 10000 { svr := newGracefulCloseTestService() start := make(chan struct{}) var wg sync.WaitGroup wg.Add(2) go func() { defer wg.Done() <-start svr.GracefulClose(time.Duration(i)) }() go func() { defer wg.Done() <-start svr.stop() }() close(start) wg.Wait() } } func TestGracefulCloseDoesNotBlockDuringStop(t *testing.T) { const gracefulDuration = 200 * time.Millisecond svr := newGracefulCloseTestService() svr.GracefulClose(gracefulDuration) stopDone := make(chan struct{}) go func() { svr.stop() close(stopDone) }() defer func() { select { case <-stopDone: case <-time.After(time.Second): t.Error("stop did not finish") } }() deadline := time.Now().Add(time.Second) for svr.ctlMu.TryLock() { svr.ctlMu.Unlock() if time.Now().After(deadline) { t.Fatal("stop did not acquire ctlMu") } time.Sleep(time.Millisecond) } start := time.Now() svr.GracefulClose(0) if elapsed := time.Since(start); elapsed >= gracefulDuration/2 { t.Fatalf("GracefulClose blocked for %v while stop was waiting", elapsed) } }