diff --git a/internal/oci/runtime_oci.go b/internal/oci/runtime_oci.go index ee3ba8ef13f..7dfa7bd2cfa 100644 --- a/internal/oci/runtime_oci.go +++ b/internal/oci/runtime_oci.go @@ -158,24 +158,37 @@ func (r *runtimeOCI) CreateContainer(c *Container, cgroupParent string) (retErr childPipe.Close() childStartPipe.Close() - // Platform specific container setup - if err := r.createContainerPlatform(c, cgroupParent, cmd.Process.Pid); err != nil { - if killErr := cmd.Process.Kill(); killErr != nil { - return errors.Wrap(err, killErr.Error()) + // Create new scope to reduce cleanup code. + if err := func() (retErr error) { + defer func() { + if retErr != nil { + // We need to always kill and wait on this process. + // Failing to do so will cause us to leak a zombie. + killErr := cmd.Process.Kill() + waitErr := cmd.Wait() + if killErr != nil { + retErr = errors.Wrapf(retErr, "failed to kill %+v after failing with", killErr) + } + // Per https://pkg.go.dev/os#ProcessState.ExitCode, the exit code is -1 when the process died because + // of a signal. We expect this in this case, as we've just killed it with a signal. Don't append the + // error in this case to reduce noise. + if exitErr, ok := waitErr.(*exec.ExitError); !ok || exitErr.ExitCode() != -1 { + retErr = errors.Wrapf(retErr, "failed to wait %+v after failing with", waitErr) + } + } + }() + // Platform specific container setup + if err := r.createContainerPlatform(c, cgroupParent, cmd.Process.Pid); err != nil { + return err } - return err - } - /* We set the cgroup, now the child can start creating children */ - someData := []byte{0} - _, err = parentStartPipe.Write(someData) - if err != nil { - if waitErr := cmd.Wait(); waitErr != nil { - return errors.Wrap(err, waitErr.Error()) - } + /* We set the cgroup, now the child can start creating children */ + someData := []byte{0} + _, err = parentStartPipe.Write(someData) + return err + }(); err != nil { return err } - /* Wait for initial setup and fork, and reap child */ err = cmd.Wait() if err != nil { diff --git a/test/mocks/cmdrunner/cmdrunner.go b/test/mocks/cmdrunner/cmdrunner.go index 48e22c50458..2f77435fb25 100644 --- a/test/mocks/cmdrunner/cmdrunner.go +++ b/test/mocks/cmdrunner/cmdrunner.go @@ -5,36 +5,35 @@ package cmdrunnermock import ( + gomock "github.com/golang/mock/gomock" exec "os/exec" reflect "reflect" - - gomock "github.com/golang/mock/gomock" ) -// MockCommandRunner is a mock of CommandRunner interface. +// MockCommandRunner is a mock of CommandRunner interface type MockCommandRunner struct { ctrl *gomock.Controller recorder *MockCommandRunnerMockRecorder } -// MockCommandRunnerMockRecorder is the mock recorder for MockCommandRunner. +// MockCommandRunnerMockRecorder is the mock recorder for MockCommandRunner type MockCommandRunnerMockRecorder struct { mock *MockCommandRunner } -// NewMockCommandRunner creates a new mock instance. +// NewMockCommandRunner creates a new mock instance func NewMockCommandRunner(ctrl *gomock.Controller) *MockCommandRunner { mock := &MockCommandRunner{ctrl: ctrl} mock.recorder = &MockCommandRunnerMockRecorder{mock} return mock } -// EXPECT returns an object that allows the caller to indicate expected use. +// EXPECT returns an object that allows the caller to indicate expected use func (m *MockCommandRunner) EXPECT() *MockCommandRunnerMockRecorder { return m.recorder } -// CombinedOutput mocks base method. +// CombinedOutput mocks base method func (m *MockCommandRunner) CombinedOutput(arg0 string, arg1 ...string) ([]byte, error) { m.ctrl.T.Helper() varargs := []interface{}{arg0} @@ -47,14 +46,14 @@ func (m *MockCommandRunner) CombinedOutput(arg0 string, arg1 ...string) ([]byte, return ret0, ret1 } -// CombinedOutput indicates an expected call of CombinedOutput. +// CombinedOutput indicates an expected call of CombinedOutput func (mr *MockCommandRunnerMockRecorder) CombinedOutput(arg0 interface{}, arg1 ...interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() varargs := append([]interface{}{arg0}, arg1...) return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CombinedOutput", reflect.TypeOf((*MockCommandRunner)(nil).CombinedOutput), varargs...) } -// Command mocks base method. +// Command mocks base method func (m *MockCommandRunner) Command(arg0 string, arg1 ...string) *exec.Cmd { m.ctrl.T.Helper() varargs := []interface{}{arg0} @@ -66,7 +65,7 @@ func (m *MockCommandRunner) Command(arg0 string, arg1 ...string) *exec.Cmd { return ret0 } -// Command indicates an expected call of Command. +// Command indicates an expected call of Command func (mr *MockCommandRunnerMockRecorder) Command(arg0 interface{}, arg1 ...interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() varargs := append([]interface{}{arg0}, arg1...)