Run exec commands in dedicated process groups
This commit is contained in:
parent
53eb992a09
commit
1f4bcba670
|
|
@ -96,6 +96,11 @@ func (rpc *RPC) Exec(stream grpc.BidiStreamingServer[ExecRequest, ExecResponse])
|
|||
return nil
|
||||
}
|
||||
|
||||
// Kill the whole process group when the exec stream is canceled
|
||||
cmd.Cancel = func() error {
|
||||
return signalProcessGroup(cmd.Process, syscall.SIGKILL)
|
||||
}
|
||||
|
||||
var stdin io.WriteCloser
|
||||
var stdout, stderr io.ReadCloser
|
||||
var ptmx *os.File
|
||||
|
|
@ -112,6 +117,9 @@ func (rpc *RPC) Exec(stream grpc.BidiStreamingServer[ExecRequest, ExecResponse])
|
|||
stdout = ptmx
|
||||
stderr = ptmx
|
||||
} else {
|
||||
// Start the command in its own process group so signals reach all descendants
|
||||
cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true}
|
||||
|
||||
if firstExecRequestCommand.Command.Interactive {
|
||||
stdin, err = cmd.StdinPipe()
|
||||
if err != nil {
|
||||
|
|
@ -227,7 +235,7 @@ func (rpc *RPC) Exec(stream grpc.BidiStreamingServer[ExecRequest, ExecResponse])
|
|||
return
|
||||
}
|
||||
|
||||
if err := cmd.Process.Signal(signal); err != nil && !errors.Is(err, os.ErrProcessDone) {
|
||||
if err := signalProcessGroup(cmd.Process, signal); err != nil && !errors.Is(err, os.ErrProcessDone) {
|
||||
fromClientErrCh <- err
|
||||
|
||||
return
|
||||
|
|
@ -340,6 +348,19 @@ func (rpc *RPC) Exec(stream grpc.BidiStreamingServer[ExecRequest, ExecResponse])
|
|||
})
|
||||
}
|
||||
|
||||
func signalProcessGroup(process *os.Process, signal syscall.Signal) error {
|
||||
if err := syscall.Kill(-process.Pid, signal); err != nil {
|
||||
// Translate a missing process group into the process-finished error expected by os/exec
|
||||
if errors.Is(err, syscall.ESRCH) {
|
||||
return os.ErrProcessDone
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func sendStartSuccess(stream grpc.BidiStreamingServer[ExecRequest, ExecResponse]) error {
|
||||
return stream.Send(&ExecResponse{
|
||||
Type: &ExecResponse_Started_{
|
||||
|
|
|
|||
|
|
@ -152,6 +152,23 @@ func TestExecSignalsProcess(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestExecSignalsProcessGroup(t *testing.T) {
|
||||
stream, result := startExecTest(t, &ExecRequest_Command{
|
||||
Name: "/bin/sh",
|
||||
Args: []string{"-c", "sleep 30 & printf ready; wait"},
|
||||
})
|
||||
require.NotNil(t, receiveExecResponse(t, stream).GetStarted())
|
||||
require.Equal(t, []byte("ready"), receiveExecResponse(t, stream).GetStandardOutput().GetData())
|
||||
|
||||
stream.requests <- &ExecRequest{
|
||||
Type: &ExecRequest_Signal_{Signal: ExecRequest_SIGNAL_SIGTERM},
|
||||
}
|
||||
|
||||
response := receiveExecResponse(t, stream)
|
||||
require.EqualValues(t, signalExitCodeOffset+syscall.SIGTERM, response.GetExit().GetCode())
|
||||
require.NoError(t, receiveExecResult(t, result))
|
||||
}
|
||||
|
||||
func startExecTest(
|
||||
t *testing.T,
|
||||
command *ExecRequest_Command,
|
||||
|
|
|
|||
Loading…
Reference in New Issue