diff --git a/pexec/managed_process.go b/pexec/managed_process.go index 610eb5ee..8b7b277d 100644 --- a/pexec/managed_process.go +++ b/pexec/managed_process.go @@ -32,6 +32,10 @@ type ManagedProcess interface { // Stop signals and waits for the process to stop. An error is returned if // there's any system level issue stopping the process. Stop() error + + // Status return nil when the process is both alive and owned. + // If err is non-nil, process may be a) alive but not owned or b) dead. + Status() error } // NewManagedProcess returns a new, unstarted, from the given configuration. @@ -102,6 +106,12 @@ func (p *managedProcess) ID() string { return p.id } +func (p *managedProcess) Status() error { + p.mu.Lock() + defer p.mu.Unlock() + return p.cmd.Process.Signal(syscall.Signal(0)) +} + func (p *managedProcess) Start(ctx context.Context) error { p.mu.Lock() defer p.mu.Unlock() diff --git a/pexec/managed_process_test.go b/pexec/managed_process_test.go index 7c78e6d6..50c0368d 100644 --- a/pexec/managed_process_test.go +++ b/pexec/managed_process_test.go @@ -182,7 +182,9 @@ func TestManagedProcessStart(t *testing.T) { <-watcher.Events + test.That(t, proc.Status(), test.ShouldBeNil) test.That(t, proc.Stop(), test.ShouldBeNil) + test.That(t, proc.Status().Error(), test.ShouldContainSubstring, "process already finished") rd, err := os.ReadFile(tempFile.Name()) test.That(t, err, test.ShouldBeNil) @@ -348,6 +350,7 @@ func TestManagedProcessStop(t *testing.T) { <-watcher.Events + test.That(t, proc.Status(), test.ShouldBeNil) err = proc.Stop() if runtime.GOOS == "windows" { test.That(t, err, test.ShouldBeNil) @@ -355,6 +358,7 @@ func TestManagedProcessStop(t *testing.T) { test.That(t, err, test.ShouldNotBeNil) test.That(t, err.Error(), test.ShouldContainSubstring, "exit status 1") } + test.That(t, proc.Status(), test.ShouldNotBeNil) proc = NewManagedProcess(ProcessConfig{ Name: "bash", @@ -408,9 +412,11 @@ done`, tempFile.Name())) }, logger) test.That(t, proc.Start(context.Background()), test.ShouldBeNil) <-watcher.Events + test.That(t, proc.Status(), test.ShouldBeNil) err = proc.Stop() test.That(t, err, test.ShouldNotBeNil) test.That(t, err.Error(), test.ShouldContainSubstring, "exit status 115") + test.That(t, proc.Status(), test.ShouldNotBeNil) for _, signal := range knownSignals { t.Run(fmt.Sprintf("sig=%s", sigStr(signal)), func(t *testing.T) { @@ -699,3 +705,10 @@ func (fp *fakeProcess) Stop() error { } return nil } + +func (fp *fakeProcess) Status() error { + if fp.stopErr || fp.startErr { + return errors.New("dead") + } + return nil +} diff --git a/pexec/managed_process_windows.go b/pexec/managed_process_windows.go index b19322a4..ad1034b2 100644 --- a/pexec/managed_process_windows.go +++ b/pexec/managed_process_windows.go @@ -25,6 +25,7 @@ func parseSignal(sigStr, name string) (syscall.Signal, error) { return 0, errors.New("signals not supported on Windows") } + func (p *managedProcess) sysProcAttr() (*syscall.SysProcAttr, error) { ret := &syscall.SysProcAttr{ CreationFlags: syscall.CREATE_NEW_PROCESS_GROUP,