container/init: use one channel for wait4
All checks were successful
Test / Create distribution (push) Successful in 34s
Test / Sandbox (push) Successful in 2m20s
Test / Hakurei (push) Successful in 3m12s
Test / Hpkg (push) Successful in 4m3s
Test / Sandbox (race detector) (push) Successful in 4m6s
Test / Hakurei (race detector) (push) Successful in 4m51s
Test / Flake checks (push) Successful in 1m31s

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 <cat@gensokyo.uk>
This commit is contained in:
Ophestra 2025-10-15 21:35:19 +09:00
parent 52e3324ef4
commit ae65491223
Signed by: cat
SSH Key Fingerprint: SHA256:gQ67O0enBZ7UdZypgtspB2FDM1g3GVw8nX0XSdcFw8Q
2 changed files with 25 additions and 7 deletions

View File

@ -353,10 +353,14 @@ func initEntrypoint(k syscallDispatcher, msg message.Msg) {
wpid int wpid int
wstatus WaitStatus 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) info := make(chan winfo, 1)
done := make(chan struct{})
k.new(func(k syscallDispatcher) { k.new(func(k syscallDispatcher) {
k.lockOSThread()
var ( var (
err error err error
wpid = -2 wpid = -2
@ -382,7 +386,7 @@ func initEntrypoint(k syscallDispatcher, msg message.Msg) {
k.printf(msg, "unexpected wait4 response: %v", err) k.printf(msg, "unexpected wait4 response: %v", err)
} }
close(done) close(info)
}) })
// handle signals to dump withheld messages // handle signals to dump withheld messages
@ -411,7 +415,13 @@ func initEntrypoint(k syscallDispatcher, msg message.Msg) {
msg.BeforeExit() msg.BeforeExit()
k.exit(0) 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 { if w.wpid == cmd.Process.Pid {
// initial process exited, output is most likely available again // initial process exited, output is most likely available again
msg.Resume() msg.Resume()
@ -433,10 +443,6 @@ func initEntrypoint(k syscallDispatcher, msg message.Msg) {
go func() { time.Sleep(params.AdoptWaitDelay); close(timeout) }() go func() { time.Sleep(params.AdoptWaitDelay); close(timeout) }()
} }
case <-done:
msg.BeforeExit()
k.exit(r)
case <-timeout: case <-timeout:
k.printf(msg, "timeout exceeded waiting for lingering processes") k.printf(msg, "timeout exceeded waiting for lingering processes")
msg.BeforeExit() msg.BeforeExit()

View File

@ -2081,6 +2081,8 @@ func TestInitEntrypoint(t *testing.T) {
/* wait4 */ /* wait4 */
Tracks: []stub.Expect{{Calls: []stub.Call{ 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 // 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), 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 // this terminates the goroutine at the call, preventing it from leaking while preserving behaviour
@ -2174,6 +2176,8 @@ func TestInitEntrypoint(t *testing.T) {
/* wait4 */ /* wait4 */
Tracks: []stub.Expect{{Calls: []stub.Call{ 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 // 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), call("wait4", stub.ExpectArgs{-1, nil, 0, nil, stub.PanicExit}, 0, syscall.ECHILD),
}}}, }}},
@ -2266,6 +2270,8 @@ func TestInitEntrypoint(t *testing.T) {
/* wait4 */ /* wait4 */
Tracks: []stub.Expect{{Calls: []stub.Call{ Tracks: []stub.Expect{{Calls: []stub.Call{
call("lockOSThread", stub.ExpectArgs{}, nil, nil),
call("wait4", stub.ExpectArgs{-1, syscall.WaitStatus(0xfade01ce), 0, nil}, 0xbad, 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 // 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), call("wait4", stub.ExpectArgs{-1, nil, 0, nil, 0xdeadbeef}, 0, syscall.ECHILD),
@ -2358,6 +2364,8 @@ func TestInitEntrypoint(t *testing.T) {
/* wait4 */ /* wait4 */
Tracks: []stub.Expect{{Calls: []stub.Call{ 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, 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), call("wait4", stub.ExpectArgs{-1, syscall.WaitStatus(0xdeaf), 0, nil}, 0xbabe, nil),
@ -2494,6 +2502,8 @@ func TestInitEntrypoint(t *testing.T) {
/* wait4 */ /* wait4 */
Tracks: []stub.Expect{{Calls: []stub.Call{ 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, 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), call("wait4", stub.ExpectArgs{-1, syscall.WaitStatus(0xdeaf), 0, nil}, 0xbabe, nil),
@ -2634,6 +2644,8 @@ func TestInitEntrypoint(t *testing.T) {
/* wait4 */ /* wait4 */
Tracks: []stub.Expect{{Calls: []stub.Call{ 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, 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), call("wait4", stub.ExpectArgs{-1, syscall.WaitStatus(0xdeaf), 0, nil}, 0xbabe, nil),