Fix #11710: Avoid to try to close channel twice after hitting Ctrl-C on compose up (#11719)

Ensure done channel is closed only once

Signed-off-by: Jaime Soriano Pastor <jaime.soriano@elastic.co>
This commit is contained in:
Jaime Soriano Pastor 2024-04-20 08:09:26 +02:00 committed by GitHub
parent fd532a37e7
commit 5682480726
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
1 changed files with 13 additions and 13 deletions

View File

@ -21,6 +21,7 @@ import (
"fmt" "fmt"
"os" "os"
"os/signal" "os/signal"
"sync/atomic"
"syscall" "syscall"
"github.com/compose-spec/compose-go/v2/types" "github.com/compose-spec/compose-go/v2/types"
@ -65,9 +66,10 @@ func (s *composeService) Up(ctx context.Context, project *types.Project, options
// we might miss a signal while setting up the second channel read // we might miss a signal while setting up the second channel read
// (this is also why signal.Notify is used vs signal.NotifyContext) // (this is also why signal.Notify is used vs signal.NotifyContext)
signalChan := make(chan os.Signal, 2) signalChan := make(chan os.Signal, 2)
signal.Notify(signalChan, syscall.SIGINT, syscall.SIGTERM)
defer close(signalChan) defer close(signalChan)
var isTerminated bool signal.Notify(signalChan, syscall.SIGINT, syscall.SIGTERM)
defer signal.Stop(signalChan)
var isTerminated atomic.Bool
printer := newLogPrinter(options.Start.Attach) printer := newLogPrinter(options.Start.Attach)
doneCh := make(chan bool) doneCh := make(chan bool)
@ -78,12 +80,11 @@ func (s *composeService) Up(ctx context.Context, project *types.Project, options
formatter.ClearLine() formatter.ClearLine()
fmt.Fprintln(s.stdinfo(), "Gracefully stopping... (press Ctrl+C again to force)") fmt.Fprintln(s.stdinfo(), "Gracefully stopping... (press Ctrl+C again to force)")
eg.Go(func() error { eg.Go(func() error {
err := s.Stop(context.Background(), project.Name, api.StopOptions{ err := s.Stop(context.WithoutCancel(ctx), project.Name, api.StopOptions{
Services: options.Create.Services, Services: options.Create.Services,
Project: project, Project: project,
}) })
isTerminated = true isTerminated.Store(true)
close(doneCh)
return err return err
}) })
first = false first = false
@ -120,7 +121,7 @@ func (s *composeService) Up(ctx context.Context, project *types.Project, options
break break
} }
eg.Go(func() error { eg.Go(func() error {
err := s.kill(context.Background(), project.Name, api.KillOptions{ err := s.kill(context.WithoutCancel(ctx), project.Name, api.KillOptions{
Services: options.Create.Services, Services: options.Create.Services,
Project: project, Project: project,
All: true, All: true,
@ -165,18 +166,17 @@ func (s *composeService) Up(ctx context.Context, project *types.Project, options
}) })
} }
// We don't use parent (cancelable) context as we manage sigterm to stop the stack // We use the parent context without cancelation as we manage sigterm to stop the stack
err = s.start(context.Background(), project.Name, options.Start, printer.HandleEvent) err = s.start(context.WithoutCancel(ctx), project.Name, options.Start, printer.HandleEvent)
if err != nil && !isTerminated { // Ignore error if the process is terminated if err != nil && !isTerminated.Load() { // Ignore error if the process is terminated
return err return err
} }
// Signal for the signal-handler goroutines to stop
close(doneCh)
printer.Stop() printer.Stop()
if !isTerminated {
// signal for the signal-handler goroutines to stop
close(doneCh)
}
err = eg.Wait().ErrorOrNil() err = eg.Wait().ErrorOrNil()
if exitCode != 0 { if exitCode != 0 {
errMsg := "" errMsg := ""