Skip to content

Commit 4bebd95

Browse files
committed
Handle signal re-raising for improved process termination behavior
1 parent f9828df commit 4bebd95

File tree

6 files changed

+72
-9
lines changed

6 files changed

+72
-9
lines changed

cmd/compose/compose.go

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import (
2727
"path/filepath"
2828
"strconv"
2929
"strings"
30+
"sync/atomic"
3031
"syscall"
3132

3233
"github.com/compose-spec/compose-go/v2/cli"
@@ -108,17 +109,24 @@ func AdaptCmd(fn CobraCommand) func(cmd *cobra.Command, args []string) error {
108109
return func(cmd *cobra.Command, args []string) error {
109110
ctx, cancel := context.WithCancel(cmd.Context())
110111

112+
var caughtSignal atomic.Value
111113
s := make(chan os.Signal, 1)
112114
signal.Notify(s, syscall.SIGTERM, syscall.SIGINT)
113115
go func() {
114-
<-s
116+
sig := <-s
117+
caughtSignal.Store(sig)
115118
cancel()
116119
signal.Stop(s)
117120
close(s)
118121
}()
119122

120123
err := fn(ctx, cmd, args)
121124
if api.IsErrCanceled(err) || errors.Is(ctx.Err(), context.Canceled) {
125+
if sig, ok := caughtSignal.Load().(os.Signal); ok {
126+
reraiseSignal(sig)
127+
// On Unix, process dies here from signal.
128+
// On Windows (or fallback), continues below.
129+
}
122130
err = dockercli.StatusError{
123131
StatusCode: 130,
124132
}

cmd/compose/signal_unix.go

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
//go:build !windows
2+
3+
/*
4+
Copyright 2020 Docker Compose CLI authors
5+
6+
Licensed under the Apache License, Version 2.0 (the "License");
7+
you may not use this file except in compliance with the License.
8+
You may obtain a copy of the License at
9+
10+
http://www.apache.org/licenses/LICENSE-2.0
11+
12+
Unless required by applicable law or agreed to in writing, software
13+
distributed under the License is distributed on an "AS IS" BASIS,
14+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
See the License for the specific language governing permissions and
16+
limitations under the License.
17+
*/
18+
19+
package compose
20+
21+
import (
22+
"os"
23+
"os/signal"
24+
"syscall"
25+
)
26+
27+
func reraiseSignal(sig os.Signal) {
28+
if s, ok := sig.(syscall.Signal); ok {
29+
signal.Reset(s)
30+
_ = syscall.Kill(syscall.Getpid(), s)
31+
}
32+
}

cmd/compose/signal_windows.go

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
//go:build windows
2+
3+
/*
4+
Copyright 2020 Docker Compose CLI authors
5+
6+
Licensed under the Apache License, Version 2.0 (the "License");
7+
you may not use this file except in compliance with the License.
8+
You may obtain a copy of the License at
9+
10+
http://www.apache.org/licenses/LICENSE-2.0
11+
12+
Unless required by applicable law or agreed to in writing, software
13+
distributed under the License is distributed on an "AS IS" BASIS,
14+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
See the License for the specific language governing permissions and
16+
limitations under the License.
17+
*/
18+
19+
package compose
20+
21+
import "os"
22+
23+
// reraiseSignal is a no-op on Windows as signal re-raising for parent
24+
// process detection is not supported. Falls through to os.Exit(130).
25+
func reraiseSignal(_ os.Signal) {}

pkg/compose/pull.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -211,7 +211,7 @@ func (s *composeService) pullServiceImage(ctx context.Context, service types.Ser
211211
Status: api.Warning,
212212
Text: "Interrupted",
213213
})
214-
return "", nil
214+
return "", ctx.Err()
215215
}
216216

217217
// check if has error and the service has a build section

pkg/e2e/cancel_test.go

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -74,9 +74,8 @@ func TestComposeCancel(t *testing.T) {
7474
case <-ctx.Done():
7575
t.Fatal("test context canceled")
7676
case err := <-processDone:
77-
// TODO(milas): Compose should really not return exit code 130 here,
78-
// this is an old hack for the compose-cli wrapper
79-
assert.Error(t, err, "exit status 130",
77+
// Process should be killed by re-raised SIGINT signal
78+
assert.ErrorContains(t, err, "signal: interrupt",
8079
"STDOUT:\n%s\nSTDERR:\n%s\n", stdout.String(), stderr.String())
8180
case <-time.After(10 * time.Second):
8281
t.Fatal("timeout waiting for Compose exit")

pkg/e2e/up_test.go

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -95,10 +95,9 @@ func TestUpDependenciesNotStopped(t *testing.T) {
9595
if err != nil {
9696
var exitErr *exec.ExitError
9797
errors.As(err, &exitErr)
98-
if exitErr.ExitCode() == -1 {
99-
t.Fatalf("`compose up` was killed: %v", err)
100-
}
101-
require.Equal(t, 130, exitErr.ExitCode())
98+
// Process is expected to die from re-raised SIGINT signal
99+
assert.Assert(t, exitErr.ExitCode() == -1 || exitErr.ExitCode() == 130,
100+
"`compose up` exited with unexpected code: %v", err)
102101
}
103102

104103
RequireServiceState(t, c, "app", "exited")

0 commit comments

Comments
 (0)