Recognize terminated runsc gofers during teardown

This commit is contained in:
ms2k 2026-08-02 19:57:36 +09:00
commit 6c570da60e

View file

@ -0,0 +1,82 @@
diff --git a/runsc/container/container.go b/runsc/container/container.go
index f0bfbf2..9fe6cf5 100644
--- a/runsc/container/container.go
+++ b/runsc/container/container.go
@@ -1321,6 +1321,14 @@ func (c *Container) waitForStopped() error {
defer cancel()
b := backoff.WithContext(backoff.NewConstantBackOff(100*time.Millisecond), ctx)
op := func() error {
+ stopped, err := processIsTerminal(goferPid)
+ if err != nil {
+ return err
+ }
+ if stopped {
+ c.GoferPid.Store(0)
+ return nil
+ }
if err := unix.Kill(goferPid, 0); err == nil {
return fmt.Errorf("gofer is still running")
}
@@ -1330,6 +1338,25 @@ func (c *Container) waitForStopped() error {
return backoff.Retry(op, b)
}
+func processIsTerminal(pid int) (bool, error) {
+ status, err := os.ReadFile(fmt.Sprintf("/proc/%d/stat", pid))
+ if errors.Is(err, os.ErrNotExist) {
+ return true, nil
+ }
+ if err != nil {
+ return false, fmt.Errorf("reading process state: %v", err)
+ }
+ return processStateIsTerminal(status)
+}
+
+func processStateIsTerminal(status []byte) (bool, error) {
+ nameEnd := strings.LastIndexByte(string(status), ')')
+ if nameEnd < 0 || nameEnd+2 >= len(status) {
+ return false, fmt.Errorf("process state is malformed")
+ }
+ return status[nameEnd+2] == 'Z' || status[nameEnd+2] == 'X', nil
+}
+
// shouldCreateDeviceGofer indicates whether a device gofer connection should
// be created.
func shouldCreateDeviceGofer(spec *specs.Spec, conf *config.Config) bool {
diff --git a/runsc/container/container_test.go b/runsc/container/container_test.go
index 8b282c4..299745f 100644
--- a/runsc/container/container_test.go
+++ b/runsc/container/container_test.go
@@ -2965,6 +2965,32 @@ func TestGoferExits(t *testing.T) {
}
}
+func TestProcessStateIsTerminal(t *testing.T) {
+ tests := []struct {
+ name string
+ status string
+ terminal bool
+ valid bool
+ }{
+ {name: "running", status: "123 (gofer) S 1", valid: true},
+ {name: "zombie", status: "123 (gofer) Z 1", terminal: true, valid: true},
+ {name: "dead", status: "123 (gofer) X 1", terminal: true, valid: true},
+ {name: "closing parenthesis in name", status: "123 (gofer)) Z 1", terminal: true, valid: true},
+ {name: "malformed", status: "123 gofer Z 1"},
+ }
+ for _, test := range tests {
+ t.Run(test.name, func(t *testing.T) {
+ terminal, err := processStateIsTerminal([]byte(test.status))
+ if (err == nil) != test.valid {
+ t.Fatalf("processStateIsTerminal() error = %v, want valid %t", err, test.valid)
+ }
+ if terminal != test.terminal {
+ t.Errorf("processStateIsTerminal() = %t, want %t", terminal, test.terminal)
+ }
+ })
+ }
+}
+
func TestRootNotMount(t *testing.T) {
appSym, err := testutil.FindFile("test/cmd/test_app/test_app")
if err != nil {