From ae65491223a460b0a902993ac81ac91ca69c91fa Mon Sep 17 00:00:00 2001 From: Ophestra Date: Wed, 15 Oct 2025 21:35:19 +0900 Subject: [PATCH] container/init: use one channel for wait4 When using two channels it is possible for the other case to be reached before all pending winfo are consumed, causing incorrect reporting. Signed-off-by: Ophestra --- container/init.go | 20 +++++++++++++------- container/init_test.go | 12 ++++++++++++ 2 files changed, 25 insertions(+), 7 deletions(-) diff --git a/container/init.go b/container/init.go index ff72c40..e38127f 100644 --- a/container/init.go +++ b/container/init.go @@ -353,10 +353,14 @@ func initEntrypoint(k syscallDispatcher, msg message.Msg) { wpid int wstatus WaitStatus } + + // info is closed as the wait4 thread terminates + // when there are no longer any processes left to reap info := make(chan winfo, 1) - done := make(chan struct{}) k.new(func(k syscallDispatcher) { + k.lockOSThread() + var ( err error wpid = -2 @@ -382,7 +386,7 @@ func initEntrypoint(k syscallDispatcher, msg message.Msg) { k.printf(msg, "unexpected wait4 response: %v", err) } - close(done) + close(info) }) // handle signals to dump withheld messages @@ -411,7 +415,13 @@ func initEntrypoint(k syscallDispatcher, msg message.Msg) { msg.BeforeExit() k.exit(0) - case w := <-info: + case w, ok := <-info: + if !ok { + msg.BeforeExit() + k.exit(r) + continue // unreachable + } + if w.wpid == cmd.Process.Pid { // initial process exited, output is most likely available again msg.Resume() @@ -433,10 +443,6 @@ func initEntrypoint(k syscallDispatcher, msg message.Msg) { go func() { time.Sleep(params.AdoptWaitDelay); close(timeout) }() } - case <-done: - msg.BeforeExit() - k.exit(r) - case <-timeout: k.printf(msg, "timeout exceeded waiting for lingering processes") msg.BeforeExit() diff --git a/container/init_test.go b/container/init_test.go index 4973972..c3f2197 100644 --- a/container/init_test.go +++ b/container/init_test.go @@ -2081,6 +2081,8 @@ func TestInitEntrypoint(t *testing.T) { /* wait4 */ Tracks: []stub.Expect{{Calls: []stub.Call{ + call("lockOSThread", stub.ExpectArgs{}, nil, nil), + // magicWait4Signal as args[4] causes this to block until simulated signal is delivered call("wait4", stub.ExpectArgs{-1, syscall.WaitStatus(0xfade01ce), 0, nil, magicWait4Signal}, 0xbad, nil), // this terminates the goroutine at the call, preventing it from leaking while preserving behaviour @@ -2174,6 +2176,8 @@ func TestInitEntrypoint(t *testing.T) { /* wait4 */ Tracks: []stub.Expect{{Calls: []stub.Call{ + call("lockOSThread", stub.ExpectArgs{}, nil, nil), + // this terminates the goroutine at the call, preventing it from leaking while preserving behaviour call("wait4", stub.ExpectArgs{-1, nil, 0, nil, stub.PanicExit}, 0, syscall.ECHILD), }}}, @@ -2266,6 +2270,8 @@ func TestInitEntrypoint(t *testing.T) { /* wait4 */ Tracks: []stub.Expect{{Calls: []stub.Call{ + call("lockOSThread", stub.ExpectArgs{}, nil, nil), + call("wait4", stub.ExpectArgs{-1, syscall.WaitStatus(0xfade01ce), 0, nil}, 0xbad, nil), // this terminates the goroutine at the call, preventing it from leaking while preserving behaviour call("wait4", stub.ExpectArgs{-1, nil, 0, nil, 0xdeadbeef}, 0, syscall.ECHILD), @@ -2358,6 +2364,8 @@ func TestInitEntrypoint(t *testing.T) { /* wait4 */ Tracks: []stub.Expect{{Calls: []stub.Call{ + call("lockOSThread", stub.ExpectArgs{}, nil, nil), + call("wait4", stub.ExpectArgs{-1, nil, 0, nil}, 0, syscall.EINTR), call("wait4", stub.ExpectArgs{-1, nil, 0, nil}, 0, syscall.EINTR), call("wait4", stub.ExpectArgs{-1, syscall.WaitStatus(0xdeaf), 0, nil}, 0xbabe, nil), @@ -2494,6 +2502,8 @@ func TestInitEntrypoint(t *testing.T) { /* wait4 */ Tracks: []stub.Expect{{Calls: []stub.Call{ + call("lockOSThread", stub.ExpectArgs{}, nil, nil), + call("wait4", stub.ExpectArgs{-1, nil, 0, nil}, 0, syscall.EINTR), call("wait4", stub.ExpectArgs{-1, nil, 0, nil}, 0, syscall.EINTR), call("wait4", stub.ExpectArgs{-1, syscall.WaitStatus(0xdeaf), 0, nil}, 0xbabe, nil), @@ -2634,6 +2644,8 @@ func TestInitEntrypoint(t *testing.T) { /* wait4 */ Tracks: []stub.Expect{{Calls: []stub.Call{ + call("lockOSThread", stub.ExpectArgs{}, nil, nil), + call("wait4", stub.ExpectArgs{-1, nil, 0, nil}, 0, syscall.EINTR), call("wait4", stub.ExpectArgs{-1, nil, 0, nil}, 0, syscall.EINTR), call("wait4", stub.ExpectArgs{-1, syscall.WaitStatus(0xdeaf), 0, nil}, 0xbabe, nil),