-
Notifications
You must be signed in to change notification settings - Fork 17.9k
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. |
How this should this proposal interact with #56986 (extended backwards compatibility)?
|
I think the answer is probably a (That's probably more user-friendly than expecting users to add a |
Hmm, we also still need to clarify the questions raised in #48157 (comment). 🤔 |
Another edge case: if If Or, should the Or, should |
Another interaction: subtests. [This part was answered already]Should each subtest by default get its own If each subtest gets its own But: if we pause the parent test's timer, will that also cause its |
Another edge case: does (I would prefer “unlimited” to be |
I just realized that the subtest question is actually already answered in the initial proposal. 😅 But I do think we need to resolve that interaction with |
Hey there @bcmills thanks for all the comments 🙌 Apologies for the delay here, let me try to respond.
I agree with the option of calling SetTimeout(t) for t<=0 to mean "time out immediately". If nothing is set, it still defaults to the value of
I was not familiar with the proposal. All in all, I think that the percentage of tests that would take >1m and whose authors would not have set an explicit timeout will be low, but I'm happy to do something to avoid breaking them if you think it makes sense. From these two options, I agree that a GODEBUG flag would be the better one. If you have any CLs in hand I can look for inspiration, it'd be great!
We're talking about the overall testContext's deadline that is reported by the t.Deadline method, right? My thought is no, since it applies to the entire testContext that is common for all tests being run, I think it can still work as the independent timer as it does today and have no interaction with the new timer. If we want to expose a per-test deadline, we should add a new method. What do you think?
I view these as two separate parameters (same as -fuzztime or -count) and I think they should not interact, especially as -benchtime can be of the form of a multiplier (eg. 100x). Also, implicitly extending the per-test timeout based on some external condition sounds weird to me. Isn't guaranteeing a benchmark to fail the better option here? |
I discussed these questions with @rsc, and here is our thinking. We would prefer to avoid sentinel values, so
When running benchmarks, |
Thanks for the feedback @bcmills, and apologizing for the delays here. I will integrate this into my CL. As for @cespare's point, IMHO this is a separate setting and they shouldn't interact with one another. This way we both keep backwards compatibility with the overall test timer, and the new setting simply allows for more fine-grained setting. Why do you think that setting should be removed? |
I explained that in my first comment, which I linked (and presumably the up-voters of that comment agreed to some degree):
To expand slightly, there are two ways to encounter the default 10m timeout:
The default
On the contrary, these are deeply intertwined and defining the interaction between timeout settings has been a primary topic of the original proposal and subsequent discussions. |
Making the default overall test binary timeout "off" sounds fine to me. We just need to make sure the new 1m per-test timeout applies to all the kinds of tests: tests, examples, fuzzing, and benchmarking. |
The current way of panicing when the hard-coded 10s timeout is hit seems not ideal: #56238 (comment) At least it seems to confuse. |
Hey everyone 👋 I apologize in advance for the huge gap here, life got in the way :) I'm back and will try to implement feedback on CL 526355. (cc @cespare who had reached out about it). So if I understand correctly:
I'll rebase and amend the CL, and try to introduce some more correctness tests. Thanks for the keeping the conversation rolling! |
It sounds like we want to say something like:
|
Thanks for the focused summary here! One thing that we didn't explicitly outline is the relationship with -benchtime. I'm looking towards simplifying things and leaving them separate so if -benchtime > -testtimeout the benchmark can time out. |
@tpaschalis #69181 is specifically focused on the cmd/go "kill" timeout (not part of the testing package at all). As part of this proposal here, we are going to get rid of the default 10m timeout that cmd/go sets -- that means that, by default, we cannot set a kill timer at all, so #69181 becomes less pressing. (Perhaps at that point, since the kill timer will rarely be used anyway, we should just get rid of it.) Regarding the interaction of Assuming that's the case, then I think that will be OK. I'm a tiny bit hesitant because it's not that uncommon to have big, slow benchmarks that need quite a lot of time to set up and run even for a single execution. However, if you hit a 1m timeout and have to run it again with a different |
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: