99 "os/exec"
1010 "sync/atomic"
1111 "syscall"
12- "time"
1312
1413 "golang.org/x/sync/errgroup"
1514)
@@ -98,11 +97,8 @@ func (s *commandStage) Start(
9897 })
9998 }
10099
101- // Put the command in its own process group:
102- if s .cmd .SysProcAttr == nil {
103- s .cmd .SysProcAttr = & syscall.SysProcAttr {}
104- }
105- s .cmd .SysProcAttr .Setpgid = true
100+ // Put the command in its own process group, if possible:
101+ s .runInOwnProcessGroup ()
106102
107103 if err := s .cmd .Start (); err != nil {
108104 return nil , err
@@ -122,53 +118,6 @@ func (s *commandStage) Start(
122118 return stdout , nil
123119}
124120
125- // kill is called to kill the process if the context expires. `err` is
126- // the corresponding value of `Context.Err()`.
127- func (s * commandStage ) kill (err error ) {
128- // I believe that the calls to `syscall.Kill()` in this method are
129- // racy. It could be that s.cmd.Wait() succeeds immediately before
130- // this call, in which case the process group wouldn't exist
131- // anymore. But I don't see any way to avoid this without
132- // duplicating a lot of code from `exec.Cmd`. (`os.Cmd.Kill()` and
133- // `os.Cmd.Signal()` appear to be race-free, but only because they
134- // use internal synchronization. But those methods only kill the
135- // process, not the process group, so they are not suitable here.
136-
137- // We started the process with PGID == PID:
138- pid := s .cmd .Process .Pid
139- select {
140- case <- s .done :
141- // Process has ended; no need to kill it again.
142- return
143- default :
144- }
145-
146- // Record the `ctx.Err()`, which will be used as the error result
147- // for this stage.
148- s .ctxErr .Store (err )
149-
150- // First try to kill using a relatively gentle signal so that
151- // the processes have a chance to clean up after themselves:
152- _ = syscall .Kill (- pid , syscall .SIGTERM )
153-
154- // Well-behaved processes should commit suicide after the above,
155- // but if they don't exit within 2s, murder the whole lot of them:
156- go func () {
157- // Use an explicit `time.Timer` rather than `time.After()` so
158- // that we can stop it (freeing resources) promptly if the
159- // command exits before the timer triggers.
160- timer := time .NewTimer (2 * time .Second )
161- defer timer .Stop ()
162-
163- select {
164- case <- s .done :
165- // Process has ended; no need to kill it again.
166- case <- timer .C :
167- _ = syscall .Kill (- pid , syscall .SIGKILL )
168- }
169- }()
170- }
171-
172121// filterCmdError interprets `err`, which was returned by `Cmd.Wait()`
173122// (possibly `nil`), possibly modifying it or ignoring it. It returns
174123// the error that should actually be returned to the caller (possibly
@@ -186,7 +135,10 @@ func (s *commandStage) filterCmdError(err error) error {
186135 ctxErr , ok := s .ctxErr .Load ().(error )
187136 if ok {
188137 // If the process looks like it was killed by us, substitute
189- // `ctxErr` for the process's own exit error.
138+ // `ctxErr` for the process's own exit error. Note that this
139+ // doesn't do anything on Windows, where the `Signaled()`
140+ // method isn't implemented (it is hardcoded to return
141+ // `false`).
190142 ps , ok := eErr .ProcessState .Sys ().(syscall.WaitStatus )
191143 if ok && ps .Signaled () &&
192144 (ps .Signal () == syscall .SIGTERM || ps .Signal () == syscall .SIGKILL ) {
0 commit comments