mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-07-07 05:14:27 +00:00
abffa8f6c9
The process cmd, done and exitErr fields were written by Start/startCommand and the waitForCommand goroutine while IsRunning/GetErr/GetResult/Stop read them concurrently from other goroutines (the status endpoint and the check-xray job) — a data race. Guard them with a RWMutex: writers take the write lock; readers snapshot under the read lock and run any blocking syscall (Wait/Signal/Kill) on the local copy without holding it. IsRunning now uses the done channel as the exit signal instead of reading cmd.ProcessState, which races with cmd.Wait. Adds a -race regression test.
61 lines
1.1 KiB
Go
61 lines
1.1 KiB
Go
package xray
|
|
|
|
import (
|
|
"errors"
|
|
"os/exec"
|
|
"sync"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
// TestProcessLifecycleFieldsRaceSafe drives the lifecycle fields (cmd, done,
|
|
// exitErr) the way Start/startCommand and the waitForCommand goroutine do, while
|
|
// the status getters read them concurrently. Run with -race: any unsynchronized
|
|
// access to those fields is reported as a data race.
|
|
func TestProcessLifecycleFieldsRaceSafe(t *testing.T) {
|
|
p := &process{logWriter: NewLogWriter()}
|
|
|
|
var wg sync.WaitGroup
|
|
stop := make(chan struct{})
|
|
|
|
// Writer: churn cmd/done/exitErr like Start + waitForCommand.
|
|
wg.Add(1)
|
|
go func() {
|
|
defer wg.Done()
|
|
for {
|
|
select {
|
|
case <-stop:
|
|
return
|
|
default:
|
|
}
|
|
p.mu.Lock()
|
|
p.cmd = &exec.Cmd{}
|
|
p.done = make(chan struct{})
|
|
p.mu.Unlock()
|
|
p.setExitErr(errors.New("boom"))
|
|
}
|
|
}()
|
|
|
|
// Readers: the concurrent status getters.
|
|
for i := 0; i < 4; i++ {
|
|
wg.Add(1)
|
|
go func() {
|
|
defer wg.Done()
|
|
for {
|
|
select {
|
|
case <-stop:
|
|
return
|
|
default:
|
|
}
|
|
_ = p.IsRunning()
|
|
_ = p.GetErr()
|
|
_ = p.GetResult()
|
|
}
|
|
}()
|
|
}
|
|
|
|
time.Sleep(50 * time.Millisecond)
|
|
close(stop)
|
|
wg.Wait()
|
|
}
|