mirror of
https://github.com/docker/compose.git
synced 2026-10-12 02:13:08 +00:00
fix(up): drain in-flight log streams before tearing the session down
TestAttachRestart kept failing residually after the re-attach anchoring fix, and its new daemon-view dump told exactly why: the daemon holds all three 'world' lines while compose printed two. The monitor returns on the final die event — an events-channel fact — and up canceled the global context on the spot, killing the re-attach log streams with the last run's line still in flight: the exit notice outran the output that preceded it. Re-attach streams are now counted in a WaitGroup, and the monitor wrapper waits for them to reach their natural EOF — guaranteed once the containers exited — before canceling, under a bound that only protects against a wedged daemon and is skipped entirely when the context is already down (Ctrl-C). AttachRestart passes 5/5 locally with this drain; it failed within 2 CI attempts without it. Signed-off-by: Nicolas De Loof <[email protected]>
This commit is contained in:
committed by
Nicolas De loof
parent
586e6c22a1
commit
cca3bfbe6a
+40
-7
@@ -26,6 +26,7 @@ import (
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/compose-spec/compose-go/v2/types"
|
||||
"github.com/containerd/errdefs"
|
||||
@@ -71,13 +72,16 @@ func (s *composeService) Up(ctx context.Context, project *types.Project, options
|
||||
// them, the collected errors, and the application exit status.
|
||||
type upSession struct {
|
||||
*composeService
|
||||
project *types.Project
|
||||
options api.UpOptions
|
||||
printer logPrinter
|
||||
watcher *Watcher
|
||||
menu *formatter.LogKeyboard
|
||||
globalCtx context.Context
|
||||
cancel context.CancelFunc
|
||||
project *types.Project
|
||||
options api.UpOptions
|
||||
printer logPrinter
|
||||
// logStreams counts the in-flight re-attach log streams so shutdown can
|
||||
// drain them before tearing the context down — see the monitor wrapper.
|
||||
logStreams sync.WaitGroup
|
||||
watcher *Watcher
|
||||
menu *formatter.LogKeyboard
|
||||
globalCtx context.Context
|
||||
cancel context.CancelFunc
|
||||
|
||||
signalChan chan os.Signal
|
||||
isTerminated atomic.Bool
|
||||
@@ -178,6 +182,24 @@ func (s *composeService) runInteractiveUp(ctx context.Context, project *types.Pr
|
||||
|
||||
u.eg.Go(func() error {
|
||||
err := monitor.Start(globalCtx)
|
||||
// The monitor returning means every watched container is gone for
|
||||
// good — an events-channel fact. The last run's log lines may still
|
||||
// be in flight on their own connections, and canceling now would
|
||||
// drop them: the exit notice would outrun the output that preceded
|
||||
// it. The containers having exited, every follow stream terminates
|
||||
// on its own at EOF — give them a bounded window to drain before
|
||||
// the context comes down (immediately skipped when the context is
|
||||
// already canceled, e.g. Ctrl-C).
|
||||
drained := make(chan struct{})
|
||||
go func() {
|
||||
u.logStreams.Wait()
|
||||
close(drained)
|
||||
}()
|
||||
select {
|
||||
case <-drained:
|
||||
case <-time.After(logStreamDrainTimeout):
|
||||
case <-globalCtx.Done():
|
||||
}
|
||||
// cancel the global context to terminate signal-handler goroutines
|
||||
cancel()
|
||||
u.appendErr(err)
|
||||
@@ -348,6 +370,13 @@ func (u *upSession) captureExitCodeFrom() api.ContainerEventListener {
|
||||
|
||||
// followStartedContainers streams logs of containers (re)started after `up`,
|
||||
// so they are followed like the initially attached ones.
|
||||
//
|
||||
// logStreamDrainTimeout bounds the shutdown drain of these streams: EOF is
|
||||
// guaranteed once the containers exited, the bound only protects against a
|
||||
// wedged daemon holding the connection open. A variable so tests can shrink
|
||||
// it.
|
||||
var logStreamDrainTimeout = 5 * time.Second
|
||||
|
||||
func (u *upSession) followStartedContainers(attached []string) api.ContainerEventListener {
|
||||
runEnds := newRunEndTracker()
|
||||
return func(event api.ContainerEvent) {
|
||||
@@ -359,7 +388,11 @@ func (u *upSession) followStartedContainers(attached []string) api.ContainerEven
|
||||
// later, a fast run's own exit could already be recorded and the log
|
||||
// window would drop the whole run.
|
||||
since := runEnds.Since(event.ID)
|
||||
// counted before the goroutine starts so the shutdown drain can never
|
||||
// miss a stream dispatched but not yet running
|
||||
u.logStreams.Add(1)
|
||||
u.eg.Go(func() error {
|
||||
defer u.logStreams.Done()
|
||||
u.appendErr(u.streamContainerLogs(event, since))
|
||||
return nil
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user