From 8f3ee1e535a7b46c6358718d23742f71a38668bc Mon Sep 17 00:00:00 2001 From: Hashim1999164 <64767361+Hashim1999164@users.noreply.github.com> Date: Sat, 22 Aug 2026 23:16:58 +0500 Subject: [PATCH] Close the done channel so ttyWriter Done returns after cancel. Signed-off-by: Hashim1999164 <64767361+Hashim1999164@users.noreply.github.com> --- cmd/display/tty.go | 3 ++- cmd/display/tty_test.go | 20 ++++++++++++++++++++ 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/cmd/display/tty.go b/cmd/display/tty.go index 447ecd61e7..96f6c65451 100644 --- a/cmd/display/tty.go +++ b/cmd/display/tty.go @@ -172,7 +172,8 @@ func (w *ttyWriter) Start(ctx context.Context, operation string) { func (w *ttyWriter) Done(operation string, success bool) { w.print() - w.done <- true + // close never blocks if the render goroutine already exited via ctx.Done() + close(w.done) w.mtx.Lock() defer w.mtx.Unlock() if w.ticker != nil { diff --git a/cmd/display/tty_test.go b/cmd/display/tty_test.go index c6d6165c31..8b71a59212 100644 --- a/cmd/display/tty_test.go +++ b/cmd/display/tty_test.go @@ -569,6 +569,26 @@ func TestDoneDeadlockFix(t *testing.T) { } } +func TestDoneAfterContextCancelDoesNotHang(t *testing.T) { + w, _ := newTestWriter() + ctx, cancel := context.WithCancel(context.Background()) + w.Start(ctx, "down") + cancel() + // Let the render goroutine observe the cancellation and exit. + time.Sleep(100 * time.Millisecond) + + finished := make(chan struct{}) + go func() { + w.Done("down", false) + close(finished) + }() + select { + case <-finished: + case <-time.After(2 * time.Second): + t.Fatal("ttyWriter.Done blocked forever after context cancellation") + } +} + // TestAdjustLineWidth_WideProgressForcesSizeInfoDrop is the unit-level // regression test for docker/compose#13595. When progress contains the // " X.XMB / Y.YMB" size suffix and the bar makes beforeStatus large enough