Skip to content

Commit dd0803d

Browse files
committed
fix SIGTERM support to stop/kill stack
Signed-off-by: Nicolas De Loof <[email protected]>
1 parent 39008c5 commit dd0803d

File tree

2 files changed

+53
-49
lines changed

2 files changed

+53
-49
lines changed

cmd/compose/compose.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,8 @@ func AdaptCmd(fn CobraCommand) func(cmd *cobra.Command, args []string) error {
8383
go func() {
8484
<-s
8585
cancel()
86+
signal.Stop(s)
87+
close(s)
8688
}()
8789
}
8890
err := fn(ctx, cmd, args)

pkg/compose/up.go

Lines changed: 51 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@ import (
2121
"fmt"
2222
"os"
2323
"os/signal"
24-
"sync"
2524
"syscall"
2625

2726
"github.com/compose-spec/compose-go/types"
@@ -55,76 +54,79 @@ func (s *composeService) Up(ctx context.Context, project *types.Project, options
5554
return err
5655
}
5756

57+
var eg multierror.Group
58+
5859
// if we get a second signal during shutdown, we kill the services
5960
// immediately, so the channel needs to have sufficient capacity or
6061
// we might miss a signal while setting up the second channel read
6162
// (this is also why signal.Notify is used vs signal.NotifyContext)
6263
signalChan := make(chan os.Signal, 2)
6364
signal.Notify(signalChan, syscall.SIGINT, syscall.SIGTERM)
64-
signalCancel := sync.OnceFunc(func() {
65-
signal.Stop(signalChan)
66-
close(signalChan)
67-
})
68-
defer signalCancel()
69-
70-
printer := newLogPrinter(options.Start.Attach)
71-
stopFunc := func() error {
72-
fmt.Fprintln(s.stdinfo(), "Aborting on container exit...")
73-
ctx := context.Background()
74-
return progress.Run(ctx, func(ctx context.Context) error {
75-
// race two goroutines - one that blocks until another signal is received
76-
// and then does a Kill() and one that immediately starts a friendly Stop()
77-
errCh := make(chan error, 1)
78-
go func() {
79-
if _, ok := <-signalChan; !ok {
80-
// channel closed, so the outer function is done, which
81-
// means the other goroutine (calling Stop()) finished
82-
return
83-
}
84-
errCh <- s.Kill(ctx, project.Name, api.KillOptions{
85-
Services: options.Create.Services,
86-
Project: project,
87-
})
88-
}()
89-
90-
go func() {
91-
errCh <- s.Stop(ctx, project.Name, api.StopOptions{
92-
Services: options.Create.Services,
93-
Project: project,
94-
})
95-
}()
96-
return <-errCh
97-
}, s.stdinfo())
98-
}
99-
65+
defer close(signalChan)
10066
var isTerminated bool
101-
var eg multierror.Group
67+
68+
doneCh := make(chan bool)
10269
eg.Go(func() error {
103-
if _, ok := <-signalChan; !ok {
104-
// function finished without receiving a signal
105-
return nil
70+
first := true
71+
for {
72+
select {
73+
case <-doneCh:
74+
return nil
75+
case <-signalChan:
76+
if first {
77+
fmt.Fprintln(s.stdinfo(), "Gracefully stopping... (press Ctrl+C again to force)")
78+
eg.Go(func() error {
79+
err := s.Stop(context.Background(), project.Name, api.StopOptions{
80+
Services: options.Create.Services,
81+
Project: project,
82+
})
83+
isTerminated = true
84+
close(doneCh)
85+
return err
86+
})
87+
first = false
88+
} else {
89+
eg.Go(func() error {
90+
return s.Kill(context.Background(), project.Name, api.KillOptions{
91+
Services: options.Create.Services,
92+
Project: project,
93+
})
94+
})
95+
return nil
96+
}
97+
}
10698
}
107-
isTerminated = true
108-
printer.Cancel()
109-
fmt.Fprintln(s.stdinfo(), "Gracefully stopping... (press Ctrl+C again to force)")
110-
return stopFunc()
11199
})
112100

101+
printer := newLogPrinter(options.Start.Attach)
102+
113103
var exitCode int
114104
eg.Go(func() error {
115-
code, err := printer.Run(options.Start.CascadeStop, options.Start.ExitCodeFrom, stopFunc)
105+
code, err := printer.Run(options.Start.CascadeStop, options.Start.ExitCodeFrom, func() error {
106+
fmt.Fprintln(s.stdinfo(), "Aborting on container exit...")
107+
return progress.Run(ctx, func(ctx context.Context) error {
108+
return s.Stop(ctx, project.Name, api.StopOptions{
109+
Services: options.Create.Services,
110+
Project: project,
111+
})
112+
}, s.stdinfo())
113+
})
116114
exitCode = code
117115
return err
118116
})
119117

120-
err = s.start(ctx, project.Name, options.Start, printer.HandleEvent)
118+
// We don't use parent (cancelable) context as we manage sigterm to stop the stack
119+
err = s.start(context.Background(), project.Name, options.Start, printer.HandleEvent)
121120
if err != nil && !isTerminated { // Ignore error if the process is terminated
122121
return err
123122
}
124123

125-
// signal for the goroutines to stop & wait for them to finish any remaining work
126-
signalCancel()
127124
printer.Stop()
125+
126+
if !isTerminated {
127+
// signal for the signal-handler goroutines to stop
128+
close(doneCh)
129+
}
128130
err = eg.Wait().ErrorOrNil()
129131
if exitCode != 0 {
130132
errMsg := ""

0 commit comments

Comments
 (0)