1
0
mirror of https://github.com/golang/go synced 2024-11-24 22:00:09 -07:00

runtime: handle timer overflow in tsleep

Make sure we never pass a timer into timerproc with
a negative duration since it will cause other timers
to never expire.

Fixes #5321.

R=golang-dev, minux.ma, remyoudompheng, mikioh.mikioh, r, bradfitz, rsc, dvyukov
CC=golang-dev
https://golang.org/cl/9035047
This commit is contained in:
Anthony Martin 2013-09-06 15:47:39 -04:00 committed by Russ Cox
parent 39e004b69e
commit 3548ab5ebb
3 changed files with 98 additions and 0 deletions

View File

@ -13,8 +13,13 @@ package time
#include "malloc.h" #include "malloc.h"
#include "race.h" #include "race.h"
enum {
debug = 0,
};
static Timers timers; static Timers timers;
static void addtimer(Timer*); static void addtimer(Timer*);
static void dumptimers(int8*);
// Package time APIs. // Package time APIs.
// Godoc uses the comments in package time, not these. // Godoc uses the comments in package time, not these.
@ -92,6 +97,11 @@ addtimer(Timer *t)
int32 n; int32 n;
Timer **nt; Timer **nt;
// when must never be negative; otherwise timerproc will overflow
// during its delta calculation and never expire other timers.
if(t->when < 0)
t->when = (1LL<<63)-1;
if(timers.len >= timers.cap) { if(timers.len >= timers.cap) {
// Grow slice. // Grow slice.
n = 16; n = 16;
@ -121,6 +131,8 @@ addtimer(Timer *t)
timers.timerproc = runtime·newproc1(&timerprocv, nil, 0, 0, addtimer); timers.timerproc = runtime·newproc1(&timerprocv, nil, 0, 0, addtimer);
timers.timerproc->issystem = true; timers.timerproc->issystem = true;
} }
if(debug)
dumptimers("addtimer");
} }
// Delete timer t from the heap. // Delete timer t from the heap.
@ -157,6 +169,8 @@ runtime·deltimer(Timer *t)
siftup(i); siftup(i);
siftdown(i); siftdown(i);
} }
if(debug)
dumptimers("deltimer");
runtime·unlock(&timers); runtime·unlock(&timers);
return true; return true;
} }
@ -285,3 +299,18 @@ siftdown(int32 i)
i = c; i = c;
} }
} }
static void
dumptimers(int8 *msg)
{
Timer *t;
int32 i;
runtime·printf("timers: %s\n", msg);
for(i = 0; i < timers.len; i++) {
t = timers.t[i];
runtime·printf("\t%d\t%p:\ti %d when %D period %D fn %p\n",
i, t, t->i, t->when, t->period, t->fv->fn);
}
runtime·printf("\n");
}

View File

@ -4,6 +4,11 @@
package time package time
import (
"errors"
"runtime"
)
func init() { func init() {
// force US/Pacific for time zone tests // force US/Pacific for time zone tests
ForceUSPacificForTesting() ForceUSPacificForTesting()
@ -11,3 +16,61 @@ func init() {
var Interrupt = interrupt var Interrupt = interrupt
var DaysIn = daysIn var DaysIn = daysIn
func empty(now int64, arg interface{}) {}
// Test that a runtimeTimer with a duration so large it overflows
// does not cause other timers to hang.
//
// This test has to be in internal_test.go since it fiddles with
// unexported data structures.
func CheckRuntimeTimerOverflow() error {
// We manually create a runtimeTimer to bypass the overflow
// detection logic in NewTimer: we're testing the underlying
// runtime.addtimer function.
r := &runtimeTimer{
when: nano() + (1<<63 - 1),
f: empty,
arg: nil,
}
startTimer(r)
const timeout = 100 * Millisecond
// Start a goroutine that should send on t.C before the timeout.
t := NewTimer(1)
defer func() {
// Subsequent tests won't work correctly if we don't stop the
// overflow timer and kick the timer proc back into service.
//
// The timer proc is now sleeping and can only be awoken by
// adding a timer to the *beginning* of the heap. We can't
// wake it up by calling NewTimer since other tests may have
// left timers running that should have expired before ours.
// Instead we zero the overflow timer duration and start it
// once more.
stopTimer(r)
t.Stop()
r.when = 0
startTimer(r)
}()
// Try to receive from t.C before the timeout. It will succeed
// iff the previous sleep was able to finish. We're forced to
// spin and yield after trying to receive since we can't start
// any more timers (they might hang due to the same bug we're
// now testing).
stop := Now().Add(timeout)
for {
select {
case <-t.C:
return nil // It worked!
default:
if Now().After(stop) {
return errors.New("runtime timer stuck: overflow in addtimer")
}
runtime.Gosched()
}
}
}

View File

@ -396,3 +396,9 @@ func TestIssue5745(t *testing.T) {
timer.Stop() timer.Stop()
t.Error("Should be unreachable.") t.Error("Should be unreachable.")
} }
func TestOverflowRuntimeTimer(t *testing.T) {
if err := CheckRuntimeTimerOverflow(); err != nil {
t.Fatalf(err.Error())
}
}