Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion cmd/compose/compose.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import (
"path/filepath"
"strconv"
"strings"
"sync/atomic"
"syscall"

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

var caughtSignal atomic.Value
s := make(chan os.Signal, 1)
signal.Notify(s, syscall.SIGTERM, syscall.SIGINT)
go func() {
<-s
sig := <-s
caughtSignal.Store(sig)
cancel()
signal.Stop(s)
close(s)
}()

err := fn(ctx, cmd, args)
if api.IsErrCanceled(err) || errors.Is(ctx.Err(), context.Canceled) {
if sig, ok := caughtSignal.Load().(os.Signal); ok {
reraiseSignal(sig)
// On Unix, process dies here from signal.
// On Windows (or fallback), continues below.
}
err = dockercli.StatusError{
StatusCode: 130,
}
Expand Down
32 changes: 32 additions & 0 deletions cmd/compose/signal_unix.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
//go:build !windows

/*
Copyright 2020 Docker Compose CLI authors

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package compose

import (
"os"
"os/signal"
"syscall"
)

func reraiseSignal(sig os.Signal) {
if s, ok := sig.(syscall.Signal); ok {
signal.Reset(s)
_ = syscall.Kill(syscall.Getpid(), s)
}
}
25 changes: 25 additions & 0 deletions cmd/compose/signal_windows.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
//go:build windows

/*
Copyright 2020 Docker Compose CLI authors

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package compose

import "os"

// reraiseSignal is a no-op on Windows as signal re-raising for parent
// process detection is not supported. Falls through to os.Exit(130).
func reraiseSignal(_ os.Signal) {}
2 changes: 1 addition & 1 deletion pkg/compose/pull.go
Original file line number Diff line number Diff line change
Expand Up @@ -211,7 +211,7 @@ func (s *composeService) pullServiceImage(ctx context.Context, service types.Ser
Status: api.Warning,
Text: "Interrupted",
})
return "", nil
return "", ctx.Err()
}

// check if has error and the service has a build section
Expand Down
5 changes: 2 additions & 3 deletions pkg/e2e/cancel_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,9 +74,8 @@ func TestComposeCancel(t *testing.T) {
case <-ctx.Done():
t.Fatal("test context canceled")
case err := <-processDone:
// TODO(milas): Compose should really not return exit code 130 here,
// this is an old hack for the compose-cli wrapper
assert.Error(t, err, "exit status 130",
// Process should be killed by re-raised SIGINT signal
assert.ErrorContains(t, err, "signal: interrupt",
"STDOUT:\n%s\nSTDERR:\n%s\n", stdout.String(), stderr.String())
case <-time.After(10 * time.Second):
t.Fatal("timeout waiting for Compose exit")
Expand Down
10 changes: 6 additions & 4 deletions pkg/e2e/up_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -94,11 +94,13 @@ func TestUpDependenciesNotStopped(t *testing.T) {
err = cmd.Wait()
if err != nil {
var exitErr *exec.ExitError
errors.As(err, &exitErr)
if exitErr.ExitCode() == -1 {
t.Fatalf("`compose up` was killed: %v", err)
if !errors.As(err, &exitErr) {
t.Fatalf("`compose up` failed with non-exit error: %v", err)
}
require.Equal(t, 130, exitErr.ExitCode())
// Process is expected to die from re-raised SIGINT signal (exit code -1).
// If signal re-raise doesn't terminate the process, the fallback path exits with code 130.
assert.Assert(t, exitErr.ExitCode() == -1 || exitErr.ExitCode() == 130,
"`compose up` exited with unexpected code: %d (%v)", exitErr.ExitCode(), err)
}

RequireServiceState(t, c, "app", "exited")
Expand Down