Skip to content

Commit 88a36c9

Browse files
Bryan C. Millsgopherbot
Bryan C. Mills
authored andcommitted
cmd/go/internal/test: scale the WaitDelay for test subprocesses
Prior to CL 456116 we had an arbitrary 5-second delay after a test times out before we kill the test. In CL 456116, I reused that arbitrary 5-second delay as the WaitDelay as well, but on slower builders it does not seem to be generous enough. Instead of hard-coding the delay, for tests with a finite timout we now use a hard-coded fraction of the overall timeout. That will probably give delays that are longer than strictly necessary for very long timeouts, but if the user is willing to wait for a very long timeout they can probably wait a little longer for I/O too. Fixes #58230. Updates #24050. Change-Id: Ifbf3e576c034c721aa00cd17bf88563474b09955 Reviewed-on: https://go-review.googlesource.com/c/go/+/464555 TryBot-Result: Gopher Robot <[email protected]> Reviewed-by: Michael Pratt <[email protected]> Run-TryBot: Bryan Mills <[email protected]> Auto-Submit: Bryan Mills <[email protected]>
1 parent fc86770 commit 88a36c9

File tree

2 files changed

+35
-5
lines changed

2 files changed

+35
-5
lines changed

src/cmd/go/internal/test/test.go

+33-3
Original file line numberDiff line numberDiff line change
@@ -580,6 +580,7 @@ var (
580580
testHelp bool // -help option passed to test via -args
581581

582582
testKillTimeout = 100 * 365 * 24 * time.Hour // backup alarm; defaults to about a century if no timeout is set
583+
testWaitDelay time.Duration // how long to wait for output to close after a test binary exits; zero means unlimited
583584
testCacheExpire time.Time // ignore cached test results before this time
584585

585586
testBlockProfile, testCPUProfile, testMemProfile, testMutexProfile, testTrace string // profiling flag that limits test to one package
@@ -754,7 +755,35 @@ func runTest(ctx context.Context, cmd *base.Command, args []string) {
754755
// Don't set this if fuzzing, since it should be able to run
755756
// indefinitely.
756757
if testTimeout > 0 && testFuzz == "" {
757-
testKillTimeout = testTimeout + 1*time.Minute
758+
// The WaitDelay for the test process depends on both the OS I/O and
759+
// scheduling overhead and the amount of I/O generated by the test just
760+
// before it exits. We set the minimum at 5 seconds to account for the OS
761+
// overhead, and scale it up from there proportional to the overall test
762+
// timeout on the assumption that the time to write and read a goroutine
763+
// dump from a timed-out test process scales roughly with the overall
764+
// running time of the test.
765+
//
766+
// This is probably too generous when the timeout is very long, but it seems
767+
// better to hard-code a scale factor than to hard-code a constant delay.
768+
if wd := testTimeout / 10; wd < 5*time.Second {
769+
testWaitDelay = 5 * time.Second
770+
} else {
771+
testWaitDelay = wd
772+
}
773+
774+
// We expect the test binary to terminate itself (and dump stacks) after
775+
// exactly testTimeout. We give it up to one WaitDelay or one minute,
776+
// whichever is longer, to finish dumping stacks before we send it an
777+
// external signal: if the process has a lot of goroutines, dumping stacks
778+
// after the timeout can take a while.
779+
//
780+
// After the signal is delivered, the test process may have up to one
781+
// additional WaitDelay to finish writing its output streams.
782+
if testWaitDelay < 1*time.Minute {
783+
testKillTimeout = testTimeout + 1*time.Minute
784+
} else {
785+
testKillTimeout = testTimeout + testWaitDelay
786+
}
758787
}
759788

760789
// Read testcache expiration time, if present.
@@ -1336,8 +1365,7 @@ func (r *runTestActor) Act(b *work.Builder, ctx context.Context, a *work.Action)
13361365
}
13371366
return err
13381367
}
1339-
// Give the test five seconds to exit after the signal before resorting to Kill.
1340-
cmd.WaitDelay = 5 * time.Second
1368+
cmd.WaitDelay = testWaitDelay
13411369

13421370
base.StartSigHandlers()
13431371
t0 := time.Now()
@@ -1375,6 +1403,8 @@ func (r *runTestActor) Act(b *work.Builder, ctx context.Context, a *work.Action)
13751403
fmt.Fprintf(cmd.Stdout, "*** Test killed with %v: ran too long (%v).\n", base.SignalTrace, testKillTimeout)
13761404
} else if cancelKilled {
13771405
fmt.Fprintf(cmd.Stdout, "*** Test killed: ran too long (%v).\n", testKillTimeout)
1406+
} else if errors.Is(err, exec.ErrWaitDelay) {
1407+
fmt.Fprintf(cmd.Stdout, "*** Test I/O incomplete %v after exiting.\n", cmd.WaitDelay)
13781408
}
13791409
var ee *exec.ExitError
13801410
if len(out) == 0 || !errors.As(err, &ee) || !ee.Exited() {

src/cmd/go/testdata/script/test_timeout_stdin.txt

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# Regression test for https://go.dev/issue/24050:
22
# a test that exits with an I/O stream held open
33
# should fail after a reasonable delay, not wait forever.
4-
# (As of the time of writing, that delay is 5 seconds,
4+
# (As of the time of writing, that delay is 10% of the timeout,
55
# but this test does not depend on its specific value.)
66

77
[short] skip 'runs a test that hangs until its WaitDelay expires'
@@ -18,7 +18,7 @@
1818
# exit due to a failed write to that pipe.
1919

2020
stdout '^--- PASS: TestOrphanCmd .*\nPASS\nstdin closed'
21-
stdout '^exec: WaitDelay expired before I/O complete\nFAIL\s+example\s+\d+(\.\d+)?s'
21+
stdout '^\*\*\* Test I/O incomplete \d+.* after exiting\.\nexec: WaitDelay expired before I/O complete\nFAIL\s+example\s+\d+(\.\d+)?s'
2222

2323
-- go.mod --
2424
module example

0 commit comments

Comments
 (0)