runtime, time: strictly enforce when, period constraints

timer.when must always be positive. addtimer and modtimer already check
that it is non-negative; we expand it to include zero. Also upgrade from
pinning bad values to throwing, as these values shouldn't be possible to
pass (except as below).

timeSleep may overflow timer.nextwhen. This would previously have been
pinned by resetForSleep, now we fix it manually.

runOneTimer may overflow timer.when when adding timer.period. Detect
this and pin to maxWhen.

addtimer is now too strict to allow TestOverflowRuntimeTimer to test an
overflowed timer. Such a timer should not be possible; to help guard
against accidental inclusion siftup / siftdown will check timers as it
goes. This has been replaced with tests for period and sleep overflows.

Change-Id: I17f9739e27ebcb20d87945c635050316fb8e9226
Reviewed-on: https://go-review.googlesource.com/c/go/+/274853
Trust: Michael Pratt <mpratt@google.com>
Reviewed-by: Michael Knyszek <mknyszek@google.com>
Reviewed-by: Ian Lance Taylor <iant@golang.org>
This commit is contained in:
Michael Pratt 2020-12-02 12:19:13 -05:00
parent b635e4b808
commit b78b427be5
4 changed files with 60 additions and 38 deletions

View file

@ -33,38 +33,30 @@ var DaysIn = daysIn
func empty(arg interface{}, seq uintptr) {}
// Test that a runtimeTimer with a duration so large it overflows
// does not cause other timers to hang.
// Test that a runtimeTimer with a period that would overflow when on
// expiration does not throw or cause other timers to hang.
//
// This test has to be in internal_test.go since it fiddles with
// unexported data structures.
func CheckRuntimeTimerOverflow() {
// We manually create a runtimeTimer to bypass the overflow
// detection logic in NewTimer: we're testing the underlying
// runtime.addtimer function.
func CheckRuntimeTimerPeriodOverflow() {
// We manually create a runtimeTimer with huge period, but that expires
// immediately. The public Timer interface would require waiting for
// the entire period before the first update.
r := &runtimeTimer{
when: runtimeNano() + (1<<63 - 1),
f: empty,
arg: nil,
when: runtimeNano(),
period: 1<<63 - 1,
f: empty,
arg: nil,
}
startTimer(r)
defer stopTimer(r)
// Start a goroutine that should send on t.C right away.
t := NewTimer(1)
defer func() {
stopTimer(r)
t.Stop()
}()
// If the test fails, we will hang here until the timeout in the
// testing package fires, which is 10 minutes. It would be nice to
// catch the problem sooner, but there is no reliable way to guarantee
// that timers are run without doing something involving the scheduler.
// Previous failed attempts have tried calling runtime.Gosched and
// runtime.GC, but neither is reliable. So we fall back to hope:
// We hope we don't hang here.
<-t.C
// If this test fails, we will either throw (when siftdownTimer detects
// bad when on update), or other timers will hang (if the timer in a
// heap is in a bad state). There is no reliable way to test this, but
// we wait on a short timer here as a smoke test (alternatively, timers
// in later tests may hang).
<-After(25 * Millisecond)
}
var (