fix: use atomic int for multi-worker exit code and clean up cancellation

- Replace bare int with atomic.Int32 to fix data race on ret
- Replace wg.Add/go/defer wg.Done pattern with wg.Go (Go 1.22+)
- Pass context by value instead of pointer
- Add ctx.Err() guard to suppress fatal on cancellation errors
- Add ProcessState nil check before accessing ExitCode
- Move cancel() after wg.Wait() to avoid race where a successful
  worker's deferred cancel kills siblings and triggers log.Fatal
pull/888/head
ginuerzh 2026-06-21 21:07:37 +08:00
parent b628475871
commit b985e5138c
1 changed files with 21 additions and 16 deletions

View File

@ -11,6 +11,7 @@ import (
"runtime" "runtime"
"strings" "strings"
"sync" "sync"
"sync/atomic"
"time" "time"
"github.com/go-gost/core/logger" "github.com/go-gost/core/logger"
@ -48,39 +49,43 @@ func init() {
if strings.Contains(args, " -- ") { if strings.Contains(args, " -- ") {
var ( var (
wg sync.WaitGroup wg sync.WaitGroup
ret int ret atomic.Int32
) )
ctx, cancel := context.WithCancel(context.Background()) wargsList := strings.Split(" "+args+" ", " -- ")
defer cancel()
for wid, wargs := range strings.Split(" "+args+" ", " -- ") { ctx, cancel := context.WithCancel(context.Background())
wg.Add(1)
go func(wid int, wargs string) { for wid, wargs := range wargsList {
defer wg.Done() wg.Go(func() {
defer cancel() worker(wid, strings.Split(strings.TrimSpace(wargs), " "), ctx, &ret)
worker(wid, strings.Split(wargs, " "), &ctx, &ret) })
}(wid, strings.TrimSpace(wargs))
} }
wg.Wait() wg.Wait()
cancel()
os.Exit(ret) os.Exit(int(ret.Load()))
} }
} }
func worker(id int, args []string, ctx *context.Context, ret *int) { func worker(id int, args []string, ctx context.Context, ret *atomic.Int32) {
cmd := exec.CommandContext(*ctx, os.Args[0], args...) cmd := exec.CommandContext(ctx, os.Args[0], args...)
cmd.Stdout = os.Stdout cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr cmd.Stderr = os.Stderr
cmd.Env = append(os.Environ(), fmt.Sprintf("_GOST_ID=%d", id)) cmd.Env = append(os.Environ(), fmt.Sprintf("_GOST_ID=%d", id))
if err := cmd.Run(); err != nil { if err := cmd.Run(); err != nil {
log.Fatal(err) // Context cancellation is expected when one worker exits early.
// Only log fatal on other errors.
if ctx.Err() == nil {
log.Fatal(err)
}
return
} }
if cmd.ProcessState.Exited() { if cmd.ProcessState != nil && cmd.ProcessState.Exited() {
*ret = cmd.ProcessState.ExitCode() ret.Store(int32(cmd.ProcessState.ExitCode()))
} }
} }