New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
cmd/go: add per-test timeouts #48157
Comments
I suspect this would help (at least somewhat) with the use-cases motivating #39038, too. |
This sounds great. Besides being an overall better experience when there's a hanging test, it may allow for simpler test code in some situations. Sometimes I've written tests which exercise concurrent code which, if it fails, is expected to hang/deadlock; then I spawn my own timer to fail the test if it hasn't completed after, say, 5s. Depending on whether the kill-the-whole-test behavior is acceptable, I could change these to simply use |
Would there still be a 10m default I would prefer there not to be, since hitting that timeout without hitting a 1m per-test timeout seems like a signal that we have a giant test suite operating correctly, not that we have a hanging/broken test. |
So, as per my reading, I think that's the most intuitive to the test author. If someone wants to constrain the overall run-time of tests, they'd use the global
I understand the reasoning behind this, but I'm also slightly uneasy about it. Many CIs currently use lines like
Could you expand on the reason why we can't do better? As a separate point; would https://pkg.go.dev/testing#T.Deadline be updated to take into account the timeout of the running test too? I assume it would be, but just clarifying since this proposal doesn't say. Overall I really like this idea, for the reasons Bryan and Caleb mentioned too. I think my only main worry is that setting a per-test timeout with This is not a problem for the Maybe we could also think about "testing timeout scales"; so I could run my tests in a way that all per-test timeouts are tripled if I know my machine is significantly slower than an average laptop (e.g. public CI). I think Go's builders do something similar, where very slow computers such as small ARM/RISCV boards get many timeouts multiplied to avoid benign timeouts. |
Would the failure include all messages logged up until the timeout? |
Zero net benefit, but also zero breakage. It's hard to have the latter without the former. If we were starting from scratch it would be much easier to say the two are separate. |
Yes, I would hope so. I think that's possible independent of this proposal though. Right now we just do a panic but I don't see why we couldn't go around flushing output first. (Maybe it's tricky, I don't know.) |
That's fair - I don't have a better alternative in mind. I was mainly highlighting this because we should make it clear in the release notes: that any |
We have no way to forcefully stop a goroutine asynchronously. The reason we have no way is that it is unsafe: the goroutine may be holding locks, etc. There also may be other goroutines that are running as part of the test. There is no safe way to attribute which goroutines need stopping (and then also no way to safe way to stop them). |
If we had a testing.TB Context method then the tests and benchmarks could do cooperative graceful shutdown. (Examples are a bit trickier without further changes, but are also less likely to be problematic.) |
This proposal has been added to the active column of the proposals project |
What is the benefit of having Firstly is a basic usability concern. I would suggest that applying the same per-test timeout to all tests (being run) would be unhelpful a lot of the time. However I can much more readily think of situations where you'd want to run Secondly is a more nuanced concern about how @mvdan's suggestion of a way to scale the value passed to
|
@josharian, no For an example of how this works today in the Lines 47 to 72 in 19457a5
|
@bcmills t.Deadline lets you predict when your test will time out. It doesn't provide a means to receive the information that some other test has failed (or timed out) and that as a result you should promptly shut down. |
Ah, I see what you mean. But I think that is more-or-less completely orthogonal to having per-test timeouts — that's more of an interaction between |
@joe-mann to me, most of the value of this proposal comes from The way I would use it, it's mostly about improving the handling of deadlocked tests. For example, I interact with a lot of test suites which can take several minutes to run on heavily loaded CI workers but each individual test takes a few seconds at most. It's not like I would find the longest-running test and set Then, when a test deadlocks, I think |
Are there any remaining objections? |
My only remaining worry is what I outline in the last three paragraphs in #48157 (comment) - that setting absolute A couple of comments later in the thread seem to agree that this problem warrants some attention. I personally think that some sort of multiplier seems like a sane solution, if you can roughly define what the "base" speed is - such as an average laptop from two years ago that's idle. This solution seems to have been working well enough for build.golang.org, as I understand some builders use a multiplier given slower hardware. |
@mvdan There is already an absolute duration in -timeout=, right? It seems like maybe we should have an automatic timeout scale (essentially elevating what cmd/dist does), but that could be done in a separate, orthogonal proposal. And it might not be worth doing until we have more information, since tests can of course check for environment variables and scale on their own, and people can write helpers that do that. |
@rsc can you confirm whether this proposal includes removing the 10m default |
Based on the discussion above, this proposal seems like a likely accept. |
@rsc I guess I don't have a problem with people implementing their own scaling, but it would be a shame for each project to reinvent a slightly different wheel with different standards on what the "base timeout scale" is. I am hoping we can at least get ahead of defining what the scale of the default timeout should be. If some projects use "enough even for the slowest machine", and others use "matching a modern fast machine", then adding built-in scaling at a later time won't work with the mismatching baselines. |
@mvdan, please feel free to file a proposal for a scaling mechanism. Thanks! |
No change in consensus, so accepted. |
Change https://golang.org/cl/363134 mentions this issue: |
This change sets a timeout of 10 seconds on each execution of the fuzz target, both during fuzzing and during minimization. This is not currently customizable by the user, but issue #48157 tracks this work. Deadlocks will be considered non-recoverable errors, and as such, will not be minimizable. Fixes #48591 Change-Id: Ic86e8e9e9a0255e7860f7cbf5654e832785d1cbc Reviewed-on: https://go-review.googlesource.com/c/go/+/363134 Trust: Katie Hockman <katie@golang.org> Run-TryBot: Katie Hockman <katie@golang.org> TryBot-Result: Go Bot <gobot@golang.org> Reviewed-by: Bryan C. Mills <bcmills@google.com>
I wonder if this question got lost in the conversation. I was looking at the API again, and it says:
My intuition is that we should change the docs and behavior to account for per-test timeouts, but it's worth noting that the current docs specify that it only looks at the timeout for the whole test binary. |
Change https://golang.org/cl/380497 mentions this issue: |
We care that gopls operations complete within a reasonable time. However, what is “reasonable” depends strongly on the specifics of the user and the hardware they are running on: a timeout that would be perfectly reasonable on a high-powered user workstation with little other load may be far too short on an overloaded and/or underpowered CI builder. This change adjusts the regtest runner to use the test deadline instead of an arbitrary, flag-defined timeout; we expect the user or system running the test to scale the test timeout appropriately to the specific platform and system load. When the testing package gains support for per-test timeouts (golang/go#48157), this approach will automatically apply those timeouts too. If we decide that we also want to test specific performance and/or latency targets, we can set up specific configurations for that (as either aggressive per-test timeouts or benchmarks) in a followup change. For golang/go#50582 Change-Id: I1ab11b2049effb097aa620046fe11609269f91c4 Reviewed-on: https://go-review.googlesource.com/c/tools/+/380497 Trust: Bryan Mills <bcmills@google.com> Run-TryBot: Bryan Mills <bcmills@google.com> gopls-CI: kokoro <noreply+kokoro@google.com> TryBot-Result: Gopher Robot <gobot@golang.org> Reviewed-by: Robert Findley <rfindley@google.com>
Change https://go.dev/cl/445597 mentions this issue: |
For most tests, the test's deadline itself is more appropriate than an arbitrary timeout layered atop of it (especially once #48157 is implemented), and testenv.Command already adds cleaner timeout behavior when a command would run too close to the test's deadline. That makes RunWithTimeout something of an attractive nuisance. For now, migrate the two existing uses of it to testenv.CommandContext, with a shorter timeout implemented using context.WithTimeout. As a followup, we may want to drop the extra timeouts from these invocations entirely. Updates #50436. Updates #37405. Change-Id: I16840fd36c0137b6da87ec54012b3e44661f0d08 Reviewed-on: https://go-review.googlesource.com/c/go/+/445597 Reviewed-by: Ian Lance Taylor <iant@google.com> Run-TryBot: Bryan Mills <bcmills@google.com> Auto-Submit: Bryan Mills <bcmills@google.com> Reviewed-by: Austin Clements <austin@google.com> TryBot-Result: Gopher Robot <gobot@golang.org>
For most tests, the test's deadline itself is more appropriate than an arbitrary timeout layered atop of it (especially once golang#48157 is implemented), and testenv.Command already adds cleaner timeout behavior when a command would run too close to the test's deadline. That makes RunWithTimeout something of an attractive nuisance. For now, migrate the two existing uses of it to testenv.CommandContext, with a shorter timeout implemented using context.WithTimeout. As a followup, we may want to drop the extra timeouts from these invocations entirely. Updates golang#50436. Updates golang#37405. Change-Id: I16840fd36c0137b6da87ec54012b3e44661f0d08 Reviewed-on: https://go-review.googlesource.com/c/go/+/445597 Reviewed-by: Ian Lance Taylor <iant@google.com> Run-TryBot: Bryan Mills <bcmills@google.com> Auto-Submit: Bryan Mills <bcmills@google.com> Reviewed-by: Austin Clements <austin@google.com> TryBot-Result: Gopher Robot <gobot@golang.org>
Tests have an overall timeout for the entire binary but no timeout for a specific test case.
You often want to limit any particular test case to a time much shorter than the overall binary.
I propose to add the concept of per-test (function) timeouts to the go command user experience as follows.
Each test gets a per-test timeout. The timer for a given test only ticks down when the test is running. It does not tick down when the test is blocked in t.Parallel, nor when it is blocked in t.Run running a subtest.
The default per-test case timeout is 1m (one minute). If the new
-testtimeout
flag is specified explicitly, then that sets a different default. If the-testtimeout
flag is omitted but-timeout
is specified explicitly, then that sets the default too. This way, if you have one really long test and usego test -timeout=30m
, the per-case timeout doesn't kick in after 1 minute and kill it anyway.There is a new testing.TB method
SetTimeout(d time.Duration)
that allows a test to set its own timeout. Calling SetTimeout does not reset the timer. If a test runs for 30 seconds and then callst.SetTimeout(1*time.Second)
, it gets killed for having timed out. A timeout set this way is inherited by subtests. (They each have their own timer.)When a test timeout happens, the whole process still gets killed. There's nothing we can really do about that. But the failure does say which test function timed out.
This change will help users generally, and it will also help fuzzing, because any individual invocation of a fuzz function will now have a testing.T with a 1-minute timer, providing for detection of inputs that cause infinite loops or very slow execution.
The text was updated successfully, but these errors were encountered: