Skip to content

Commit e612fb3

Browse files
committed
Stop services when startup fails
1 parent 1e2c654 commit e612fb3

5 files changed

Lines changed: 149 additions & 8 deletions

File tree

.bazelrc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,9 @@ common --nolegacy_external_runfiles
1212
common --incompatible_autoload_externally=
1313
common --repo_env=BAZEL_DO_NOT_DETECT_CPP_TOOLCHAIN=1
1414
common --repo_env=BAZEL_NO_APPLE_CPP_TOOLCHAIN=1
15+
common --@rules_go//go/config:pure
1516

1617
common:enable-reload --@rules_itest//:enable_per_service_reload
1718

1819
# TODO: Remove once transitives are fixed
1920
common --incompatible_autoload_externally=+@rules_cc
20-

cmd/svcinit/main.go

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import (
1616
"os/signal"
1717
"strconv"
1818
"strings"
19+
"sync"
1920
"syscall"
2021
"text/tabwriter"
2122
"time"
@@ -140,6 +141,17 @@ func main() {
140141
r, err := runner.New(ctx, serviceSpecs)
141142
must(err)
142143

144+
stopAll := sync.OnceValues(func() (map[string]*os.ProcessState, error) {
145+
states, err := r.StopAll()
146+
cancelFunc()
147+
return states, err
148+
})
149+
mustStopAll := func() map[string]*os.ProcessState {
150+
states, err := stopAll()
151+
must(err)
152+
return states
153+
}
154+
143155
servicesErrCh := make(chan error, len(unversionedSpecs))
144156

145157
go func() {
@@ -168,10 +180,12 @@ func main() {
168180

169181
criticalPath, err := r.StartAll(servicesErrCh)
170182
if errors.Is(err, context.Canceled) {
171-
_, err := r.StopAll()
172-
must(err)
183+
mustStopAll()
173184
return
174185
}
186+
if err != nil {
187+
mustStopAll()
188+
}
175189
must(err)
176190

177191
// API is NewWriter(output io.Writer, minwidth, tabwidth, padding int, padchar byte, flags uint) *Writer
@@ -261,8 +275,7 @@ func main() {
261275
select {
262276
case <-ctx.Done():
263277
log.Println("Shutting down services.")
264-
_, err := r.StopAll()
265-
must(err)
278+
mustStopAll()
266279
log.Println("Cleaning up.")
267280
return
268281
case ibazelCmd := <-interactiveCh:
@@ -302,20 +315,21 @@ func main() {
302315
if testErr != nil {
303316
log.Printf("Encountered error during test run: %s\n", testErr)
304317
if isOneShot {
318+
mustStopAll()
305319
os.Exit(1)
306320
}
307321
}
308322
case serviceErr := <-servicesErrCh:
309323
log.Print(serviceErr)
310324
if isOneShot {
325+
mustStopAll()
311326
log.Fatal("Service exited uncleanly, marking test as failed.\n\n")
312327
}
313328
}
314329

315330
if isOneShot {
316331
buf.WriteString("Target\tUser Time\tSystem Time\n")
317-
states, err := r.StopAll()
318-
must(err)
332+
states := mustStopAll()
319333
for label, state := range states {
320334
buf.WriteString(fmt.Sprintf("%s\t%s\t%s\n",
321335
label, state.UserTime(), state.SystemTime()))
@@ -637,4 +651,3 @@ func buildTestEnv(ports svclib.Ports) ([]string, error) {
637651

638652
return baseEnv, nil
639653
}
640-

tests/startup_failure/BUILD.bazel

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
load("@rules_itest//:itest.bzl", "itest_service", "service_test")
22
load("@rules_shell//shell:sh_binary.bzl", "sh_binary")
33
load("//:must_fail.bzl", "must_fail")
4+
load(":cleanup_failure_test.bzl", "cleanup_failure_test")
45

56
package(default_visibility = ["//visibility:public"])
67

@@ -9,6 +10,17 @@ sh_binary(
910
srcs = ["crash_immediately.sh"],
1011
)
1112

13+
sh_binary(
14+
name = "cleanup_on_sigterm",
15+
srcs = ["cleanup_on_sigterm.sh"],
16+
)
17+
18+
cleanup_failure_test(
19+
name = "cleanup_on_startup_failure_test",
20+
test = ":dependent_startup_failure_test",
21+
timeout = "short",
22+
)
23+
1224
itest_service(
1325
name = "immediate_exit_service",
1426
autoassign_port = True,
@@ -26,3 +38,31 @@ must_fail(
2638
test = "immediate_exit_service_hygiene_test",
2739
timeout = "short",
2840
)
41+
42+
itest_service(
43+
name = "cleanup_service",
44+
exe = ":cleanup_on_sigterm",
45+
shutdown_timeout = "5s",
46+
tags = ["manual"],
47+
)
48+
49+
itest_service(
50+
name = "dependent_immediate_exit_service",
51+
autoassign_port = True,
52+
deps = [":cleanup_service"],
53+
env = {
54+
"PORT": "$${PORT}",
55+
},
56+
exe = ":crash_immediately",
57+
health_check_interval = "100ms",
58+
http_health_check_address = "http://127.0.0.1:$${PORT}/health",
59+
tags = ["manual"],
60+
)
61+
62+
service_test(
63+
name = "dependent_startup_failure_test",
64+
services = [":dependent_immediate_exit_service"],
65+
tags = ["manual"],
66+
test = "@rules_itest//:exit0",
67+
timeout = "short",
68+
)
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
def _shell_quote(value):
2+
return "'" + value.replace("'", "'\"'\"'") + "'"
3+
4+
def _cleanup_failure_test_impl(ctx):
5+
test_env = ctx.attr.test[RunEnvironmentInfo].environment
6+
test_path = ctx.executable.test.short_path
7+
8+
env_lines = [
9+
"export {}={}".format(key, _shell_quote(value))
10+
for key, value in sorted(test_env.items())
11+
]
12+
13+
script = ctx.actions.declare_file(ctx.label.name + ".sh")
14+
ctx.actions.write(
15+
output = script,
16+
is_executable = True,
17+
content = """#!/usr/bin/env bash
18+
set -euo pipefail
19+
20+
cleanup_marker="${{TEST_TMPDIR}}/startup_failure_cleanup_marker"
21+
log_file="${{TEST_TMPDIR}}/dependent_startup_failure.log"
22+
test_path="${{TEST_SRCDIR}}/${{TEST_WORKSPACE}}/{test_path}"
23+
24+
rm -f "${{cleanup_marker}}"
25+
26+
{env}
27+
28+
set +e
29+
"${{test_path}}" >"${{log_file}}" 2>&1
30+
status=$?
31+
set -e
32+
33+
cat "${{log_file}}"
34+
35+
if [[ "${{status}}" -eq 0 ]]; then
36+
echo "expected dependent startup failure test to fail" >&2
37+
exit 1
38+
fi
39+
40+
if [[ ! -f "${{cleanup_marker}}" ]]; then
41+
echo "expected already-started service to receive shutdown before svcinit exited" >&2
42+
exit 1
43+
fi
44+
45+
echo "cleanup marker found: $(cat "${{cleanup_marker}}")"
46+
""".format(
47+
env = "\n".join(env_lines),
48+
test_path = test_path,
49+
),
50+
)
51+
52+
runfiles = ctx.runfiles(files = [ctx.executable.test])
53+
runfiles = runfiles.merge(ctx.attr.test.default_runfiles)
54+
55+
return [
56+
DefaultInfo(
57+
executable = script,
58+
runfiles = runfiles,
59+
),
60+
]
61+
62+
cleanup_failure_test = rule(
63+
implementation = _cleanup_failure_test_impl,
64+
attrs = {
65+
"test": attr.label(
66+
executable = True,
67+
cfg = "target",
68+
providers = [RunEnvironmentInfo],
69+
),
70+
},
71+
test = True,
72+
)
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
#!/usr/bin/env bash
2+
set -euo pipefail
3+
4+
cleanup_marker="${TEST_TMPDIR}/startup_failure_cleanup_marker"
5+
6+
cleanup() {
7+
echo "cleanup service received shutdown"
8+
echo "cleanup ran" >"${cleanup_marker}"
9+
exit 0
10+
}
11+
12+
trap cleanup TERM INT
13+
14+
while true; do
15+
sleep 1
16+
done

0 commit comments

Comments
 (0)