@@ -21,7 +21,6 @@ import (
21
21
"fmt"
22
22
"os"
23
23
"os/signal"
24
- "sync"
25
24
"syscall"
26
25
27
26
"github.com/compose-spec/compose-go/types"
@@ -55,76 +54,79 @@ func (s *composeService) Up(ctx context.Context, project *types.Project, options
55
54
return err
56
55
}
57
56
57
+ var eg multierror.Group
58
+
58
59
// if we get a second signal during shutdown, we kill the services
59
60
// immediately, so the channel needs to have sufficient capacity or
60
61
// we might miss a signal while setting up the second channel read
61
62
// (this is also why signal.Notify is used vs signal.NotifyContext)
62
63
signalChan := make (chan os.Signal , 2 )
63
64
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 )
100
66
var isTerminated bool
101
- var eg multierror.Group
67
+
68
+ doneCh := make (chan bool )
102
69
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
+ }
106
98
}
107
- isTerminated = true
108
- printer .Cancel ()
109
- fmt .Fprintln (s .stdinfo (), "Gracefully stopping... (press Ctrl+C again to force)" )
110
- return stopFunc ()
111
99
})
112
100
101
+ printer := newLogPrinter (options .Start .Attach )
102
+
113
103
var exitCode int
114
104
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
+ })
116
114
exitCode = code
117
115
return err
118
116
})
119
117
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 )
121
120
if err != nil && ! isTerminated { // Ignore error if the process is terminated
122
121
return err
123
122
}
124
123
125
- // signal for the goroutines to stop & wait for them to finish any remaining work
126
- signalCancel ()
127
124
printer .Stop ()
125
+
126
+ if ! isTerminated {
127
+ // signal for the signal-handler goroutines to stop
128
+ close (doneCh )
129
+ }
128
130
err = eg .Wait ().ErrorOrNil ()
129
131
if exitCode != 0 {
130
132
errMsg := ""
0 commit comments