Skip to content

Commit aac5453

Browse files
committed
Extract code that handles graceful termination
Signed-off-by: David Gageot <[email protected]>
1 parent 18721bb commit aac5453

File tree

4 files changed

+127
-80
lines changed

4 files changed

+127
-80
lines changed

pkg/skaffold/build/custom/custom.go

+2-41
Original file line numberDiff line numberDiff line change
@@ -20,18 +20,15 @@ import (
2020
"context"
2121
"fmt"
2222
"io"
23-
"os"
2423
"os/exec"
2524
"path/filepath"
26-
"runtime"
2725
"strings"
28-
"time"
2926

27+
"github.com/GoogleContainerTools/skaffold/pkg/skaffold/build/misc"
3028
"github.com/GoogleContainerTools/skaffold/pkg/skaffold/constants"
3129
"github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/latest"
3230
"github.com/GoogleContainerTools/skaffold/pkg/skaffold/util"
3331
"github.com/pkg/errors"
34-
"github.com/sirupsen/logrus"
3532
)
3633

3734
var (
@@ -65,43 +62,7 @@ func (b *ArtifactBuilder) Build(ctx context.Context, out io.Writer, a *latest.Ar
6562
return errors.Wrap(err, "starting cmd")
6663
}
6764

68-
return b.handleGracefulTermination(ctx, cmd)
69-
}
70-
71-
func (b *ArtifactBuilder) handleGracefulTermination(ctx context.Context, cmd *exec.Cmd) error {
72-
done := make(chan struct{})
73-
defer close(done)
74-
75-
go func() {
76-
select {
77-
case <-ctx.Done():
78-
// On windows we can't send specific signals to processes, so we kill the process immediately
79-
if runtime.GOOS == "windows" {
80-
cmd.Process.Kill()
81-
return
82-
}
83-
84-
logrus.Debugf("Sending SIGINT to process %v\n", cmd.Process.Pid)
85-
if err := cmd.Process.Signal(os.Interrupt); err != nil {
86-
// kill process on error
87-
cmd.Process.Kill()
88-
}
89-
90-
// wait 2 seconds or wait for the process to complete
91-
select {
92-
case <-time.After(2 * time.Second):
93-
logrus.Debugf("Killing process %v\n", cmd.Process.Pid)
94-
// forcefully kill process after 2 seconds grace period
95-
cmd.Process.Kill()
96-
case <-done:
97-
return
98-
}
99-
case <-done:
100-
return
101-
}
102-
}()
103-
104-
return cmd.Wait()
65+
return misc.HandleGracefulTermination(ctx, cmd)
10566
}
10667

10768
func (b *ArtifactBuilder) retrieveCmd(out io.Writer, a *latest.Artifact, tag string) (*exec.Cmd, error) {

pkg/skaffold/build/custom/custom_test.go

-39
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,9 @@ limitations under the License.
1717
package custom
1818

1919
import (
20-
"context"
2120
"io/ioutil"
2221
"os/exec"
23-
"runtime"
2422
"testing"
25-
"time"
2623

2724
"github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/latest"
2825
"github.com/GoogleContainerTools/skaffold/pkg/skaffold/util"
@@ -127,42 +124,6 @@ func TestRetrieveCmd(t *testing.T) {
127124
}
128125
}
129126

130-
func TestGracefulBuildCancel(t *testing.T) {
131-
if runtime.GOOS == "windows" {
132-
t.Skip("graceful cancel doesn't work on windows")
133-
}
134-
135-
tests := []struct {
136-
description string
137-
command string
138-
shouldErr bool
139-
}{
140-
{
141-
description: "terminate gracefully and exit 0",
142-
command: "trap 'echo trap' INT; sleep 2",
143-
}, {
144-
description: "terminate gracefully and kill process",
145-
command: "trap 'echo trap' INT; sleep 5",
146-
shouldErr: true,
147-
},
148-
}
149-
150-
for _, test := range tests {
151-
testutil.Run(t, test.description, func(t *testutil.T) {
152-
builder := NewArtifactBuilder(false, nil)
153-
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
154-
155-
cmd := exec.Command("bash", "-c", test.command)
156-
t.CheckNoError(cmd.Start())
157-
158-
err := builder.handleGracefulTermination(ctx, cmd)
159-
t.CheckError(test.shouldErr, err)
160-
161-
cancel()
162-
})
163-
}
164-
}
165-
166127
func expectedCmd(buildCommand, dir string, args, env []string) *exec.Cmd {
167128
cmd := exec.Command(buildCommand, args...)
168129
cmd.Dir = dir

pkg/skaffold/build/misc/graceful.go

+63
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
/*
2+
Copyright 2019 The Skaffold Authors
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package misc
18+
19+
import (
20+
"context"
21+
"os"
22+
"os/exec"
23+
"runtime"
24+
"time"
25+
26+
"github.com/sirupsen/logrus"
27+
)
28+
29+
func HandleGracefulTermination(ctx context.Context, cmd *exec.Cmd) error {
30+
done := make(chan struct{})
31+
defer close(done)
32+
33+
go func() {
34+
select {
35+
case <-ctx.Done():
36+
// On windows we can't send specific signals to processes, so we kill the process immediately
37+
if runtime.GOOS == "windows" {
38+
cmd.Process.Kill()
39+
return
40+
}
41+
42+
logrus.Debugf("Sending SIGINT to process %v\n", cmd.Process.Pid)
43+
if err := cmd.Process.Signal(os.Interrupt); err != nil {
44+
// kill process on error
45+
cmd.Process.Kill()
46+
}
47+
48+
// wait 2 seconds or wait for the process to complete
49+
select {
50+
case <-time.After(2 * time.Second):
51+
logrus.Debugf("Killing process %v\n", cmd.Process.Pid)
52+
// forcefully kill process after 2 seconds grace period
53+
cmd.Process.Kill()
54+
case <-done:
55+
return
56+
}
57+
case <-done:
58+
return
59+
}
60+
}()
61+
62+
return cmd.Wait()
63+
}
+62
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
/*
2+
Copyright 2019 The Skaffold Authors
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package misc
18+
19+
import (
20+
"context"
21+
"os/exec"
22+
"runtime"
23+
"testing"
24+
"time"
25+
26+
"github.com/GoogleContainerTools/skaffold/testutil"
27+
)
28+
29+
func TestGracefulBuildCancel(t *testing.T) {
30+
if runtime.GOOS == "windows" {
31+
t.Skip("graceful cancel doesn't work on windows")
32+
}
33+
34+
tests := []struct {
35+
description string
36+
command string
37+
shouldErr bool
38+
}{
39+
{
40+
description: "terminate gracefully and exit 0",
41+
command: "trap 'echo trap' INT; sleep 2",
42+
}, {
43+
description: "terminate gracefully and kill process",
44+
command: "trap 'echo trap' INT; sleep 5",
45+
shouldErr: true,
46+
},
47+
}
48+
49+
for _, test := range tests {
50+
testutil.Run(t, test.description, func(t *testutil.T) {
51+
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
52+
53+
cmd := exec.Command("bash", "-c", test.command)
54+
t.CheckNoError(cmd.Start())
55+
56+
err := HandleGracefulTermination(ctx, cmd)
57+
t.CheckError(test.shouldErr, err)
58+
59+
cancel()
60+
})
61+
}
62+
}

0 commit comments

Comments
 (0)