mirror of
https://github.com/docker/compose.git
synced 2026-10-12 02:13:08 +00:00
fix(up): stop late starters racing the on-exit abort
TestUpExitCodeFromContainerKilled hung for its full 2-minute budget on CI, and its event transcript shows why: 'Aborting on container exit' sweeps the application while the start phase — which runs on a deliberately uncancelable context for SIGTERM management — was still starting services; test-1 came up AFTER its stop and stayed up, so the monitor never drained and up never returned. The abort listener now watches the events stream past its trigger: any container started after the sweep is a late starter from that race, and gets stopped as it appears. Event-driven, so there is no listing window to miss; idempotent stops make duplicates harmless. The exit code semantics are preserved: the late starter's own 143 flows through captureExitCodeFrom exactly as when the sweep wins the race. TestUpExitCodeFrom* pass 5/5 locally. Signed-off-by: Nicolas De Loof <[email protected]>
This commit is contained in:
+26
-1
@@ -343,7 +343,19 @@ func (u *upSession) killApplication() {
|
||||
func (u *upSession) stopOnFirstExit() api.ContainerEventListener {
|
||||
once := true
|
||||
return func(event api.ContainerEvent) {
|
||||
if !once || event.Type != api.ContainerEventExited {
|
||||
if !once {
|
||||
// The abort races the start phase, which runs on a deliberately
|
||||
// uncancelable context (SIGTERM management): a container whose
|
||||
// start was in flight when the application was swept comes up
|
||||
// AFTER the stop, and would keep the session alive until its
|
||||
// natural end. The events stream reveals such late starters —
|
||||
// stop each one as it appears.
|
||||
if event.Type == api.ContainerEventStarted {
|
||||
u.stopLateStarter(event.Service)
|
||||
}
|
||||
return
|
||||
}
|
||||
if event.Type != api.ContainerEventExited {
|
||||
return
|
||||
}
|
||||
if u.options.Start.OnExit == api.CascadeFail && event.ExitCode == 0 {
|
||||
@@ -356,6 +368,19 @@ func (u *upSession) stopOnFirstExit() api.ContainerEventListener {
|
||||
}
|
||||
}
|
||||
|
||||
// stopLateStarter stops one service started after the on-exit abort swept the
|
||||
// application — see stopOnFirstExit.
|
||||
func (u *upSession) stopLateStarter(service string) {
|
||||
u.eg.Go(func() error {
|
||||
err := u.stop(context.WithoutCancel(u.globalCtx), u.project.Name, api.StopOptions{
|
||||
Services: []string{service},
|
||||
Project: u.project,
|
||||
}, u.printer.HandleEvent)
|
||||
u.appendErr(err)
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// captureExitCodeFrom captures the exit code of the first container to exit
|
||||
// for the service selected by --exit-code-from
|
||||
func (u *upSession) captureExitCodeFrom() api.ContainerEventListener {
|
||||
|
||||
Reference in New Issue
Block a user