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:
parent
39e004b69e
commit
3548ab5ebb
@ -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");
|
||||||
|
}
|
||||||
|
@ -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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -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())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user