From 48e890146698fcd3c9e0dd680202ec954032ed07 Mon Sep 17 00:00:00 2001 From: fatedier Date: Mon, 9 Mar 2026 10:28:47 +0800 Subject: [PATCH] test/e2e: optimize RunFrps/RunFrpc with process exit detection (#5225) * test/e2e: optimize RunFrps/RunFrpc with process exit detection Refactor Process to track subprocess lifecycle via a done channel, replacing direct cmd.Wait() in Stop() to avoid double-Wait races. RunFrps/RunFrpc now use select on the done channel instead of fixed sleeps, allowing short-lived processes (verify, startup failures) to return immediately while preserving existing timeout behavior for long-running daemons. * test/e2e: guard Process against double-Start and Stop-before-Start Add started flag to prevent double-Start panics and allow Stop to return immediately when the process was never started. Use sync.Once for closing the done channel as defense-in-depth against double close. --- test/e2e/framework/process.go | 10 +++++++-- test/e2e/pkg/process/process.go | 39 ++++++++++++++++++++++++++++++--- 2 files changed, 44 insertions(+), 5 deletions(-) diff --git a/test/e2e/framework/process.go b/test/e2e/framework/process.go index 6d21e3a2..e2c48427 100644 --- a/test/e2e/framework/process.go +++ b/test/e2e/framework/process.go @@ -76,7 +76,10 @@ func (f *Framework) RunFrps(args ...string) (*process.Process, string, error) { if err != nil { return p, p.Output(), err } - time.Sleep(2 * time.Second) + select { + case <-p.Done(): + case <-time.After(2 * time.Second): + } return p, p.Output(), nil } @@ -87,7 +90,10 @@ func (f *Framework) RunFrpc(args ...string) (*process.Process, string, error) { if err != nil { return p, p.Output(), err } - time.Sleep(1500 * time.Millisecond) + select { + case <-p.Done(): + case <-time.After(1500 * time.Millisecond): + } return p, p.Output(), nil } diff --git a/test/e2e/pkg/process/process.go b/test/e2e/pkg/process/process.go index 04796225..dba006c1 100644 --- a/test/e2e/pkg/process/process.go +++ b/test/e2e/pkg/process/process.go @@ -3,7 +3,9 @@ package process import ( "bytes" "context" + "errors" "os/exec" + "sync" ) type Process struct { @@ -12,6 +14,11 @@ type Process struct { errorOutput *bytes.Buffer stdOutput *bytes.Buffer + done chan struct{} + closeOne sync.Once + waitErr error + + started bool beforeStopHandler func() stopped bool } @@ -27,6 +34,7 @@ func NewWithEnvs(path string, params []string, envs []string) *Process { p := &Process{ cmd: cmd, cancel: cancel, + done: make(chan struct{}), } p.errorOutput = bytes.NewBufferString("") p.stdOutput = bytes.NewBufferString("") @@ -36,11 +44,35 @@ func NewWithEnvs(path string, params []string, envs []string) *Process { } func (p *Process) Start() error { - return p.cmd.Start() + if p.started { + return errors.New("process already started") + } + p.started = true + + err := p.cmd.Start() + if err != nil { + p.waitErr = err + p.closeDone() + return err + } + go func() { + p.waitErr = p.cmd.Wait() + p.closeDone() + }() + return nil +} + +func (p *Process) closeDone() { + p.closeOne.Do(func() { close(p.done) }) +} + +// Done returns a channel that is closed when the process exits. +func (p *Process) Done() <-chan struct{} { + return p.done } func (p *Process) Stop() error { - if p.stopped { + if p.stopped || !p.started { return nil } defer func() { @@ -50,7 +82,8 @@ func (p *Process) Stop() error { p.beforeStopHandler() } p.cancel() - return p.cmd.Wait() + <-p.done + return p.waitErr } func (p *Process) ErrorOutput() string {