mirror of
https://github.com/golang/go
synced 2024-11-19 13:14:42 -07:00
986582126a
Otherwise low-res timers cause problems at call sites that expect to be able to use 0 as meaning "no time set" and therefore expect that nanotime never returns 0 itself. For example, sched.lastpoll == 0 means no last poll. Fixes #22394. Change-Id: Iea28acfddfff6f46bc90f041ec173e0fea591285 Reviewed-on: https://go-review.googlesource.com/73410 Run-TryBot: Russ Cox <rsc@golang.org> TryBot-Result: Gobot Gobot <gobot@golang.org> Reviewed-by: Ian Lance Taylor <iant@golang.org> Reviewed-by: Austin Clements <austin@google.com>
405 lines
8.6 KiB
Go
405 lines
8.6 KiB
Go
// Copyright 2009 The Go Authors. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style
|
|
// license that can be found in the LICENSE file.
|
|
|
|
// Time-related runtime and pieces of package time.
|
|
|
|
package runtime
|
|
|
|
import (
|
|
"runtime/internal/sys"
|
|
"unsafe"
|
|
)
|
|
|
|
// Package time knows the layout of this structure.
|
|
// If this struct changes, adjust ../time/sleep.go:/runtimeTimer.
|
|
// For GOOS=nacl, package syscall knows the layout of this structure.
|
|
// If this struct changes, adjust ../syscall/net_nacl.go:/runtimeTimer.
|
|
type timer struct {
|
|
tb *timersBucket // the bucket the timer lives in
|
|
i int // heap index
|
|
|
|
// Timer wakes up at when, and then at when+period, ... (period > 0 only)
|
|
// each time calling f(arg, now) in the timer goroutine, so f must be
|
|
// a well-behaved function and not block.
|
|
when int64
|
|
period int64
|
|
f func(interface{}, uintptr)
|
|
arg interface{}
|
|
seq uintptr
|
|
}
|
|
|
|
// timersLen is the length of timers array.
|
|
//
|
|
// Ideally, this would be set to GOMAXPROCS, but that would require
|
|
// dynamic reallocation
|
|
//
|
|
// The current value is a compromise between memory usage and performance
|
|
// that should cover the majority of GOMAXPROCS values used in the wild.
|
|
const timersLen = 64
|
|
|
|
// timers contains "per-P" timer heaps.
|
|
//
|
|
// Timers are queued into timersBucket associated with the current P,
|
|
// so each P may work with its own timers independently of other P instances.
|
|
//
|
|
// Each timersBucket may be associated with multiple P
|
|
// if GOMAXPROCS > timersLen.
|
|
var timers [timersLen]struct {
|
|
timersBucket
|
|
|
|
// The padding should eliminate false sharing
|
|
// between timersBucket values.
|
|
pad [sys.CacheLineSize - unsafe.Sizeof(timersBucket{})%sys.CacheLineSize]byte
|
|
}
|
|
|
|
func (t *timer) assignBucket() *timersBucket {
|
|
id := uint8(getg().m.p.ptr().id) % timersLen
|
|
t.tb = &timers[id].timersBucket
|
|
return t.tb
|
|
}
|
|
|
|
type timersBucket struct {
|
|
lock mutex
|
|
gp *g
|
|
created bool
|
|
sleeping bool
|
|
rescheduling bool
|
|
sleepUntil int64
|
|
waitnote note
|
|
t []*timer
|
|
}
|
|
|
|
// nacl fake time support - time in nanoseconds since 1970
|
|
var faketime int64
|
|
|
|
// Package time APIs.
|
|
// Godoc uses the comments in package time, not these.
|
|
|
|
// time.now is implemented in assembly.
|
|
|
|
// timeSleep puts the current goroutine to sleep for at least ns nanoseconds.
|
|
//go:linkname timeSleep time.Sleep
|
|
func timeSleep(ns int64) {
|
|
if ns <= 0 {
|
|
return
|
|
}
|
|
|
|
gp := getg()
|
|
t := gp.timer
|
|
if t == nil {
|
|
t = new(timer)
|
|
gp.timer = t
|
|
}
|
|
*t = timer{}
|
|
t.when = nanotime() + ns
|
|
t.f = goroutineReady
|
|
t.arg = gp
|
|
tb := t.assignBucket()
|
|
lock(&tb.lock)
|
|
tb.addtimerLocked(t)
|
|
goparkunlock(&tb.lock, "sleep", traceEvGoSleep, 2)
|
|
}
|
|
|
|
// startTimer adds t to the timer heap.
|
|
//go:linkname startTimer time.startTimer
|
|
func startTimer(t *timer) {
|
|
if raceenabled {
|
|
racerelease(unsafe.Pointer(t))
|
|
}
|
|
addtimer(t)
|
|
}
|
|
|
|
// stopTimer removes t from the timer heap if it is there.
|
|
// It returns true if t was removed, false if t wasn't even there.
|
|
//go:linkname stopTimer time.stopTimer
|
|
func stopTimer(t *timer) bool {
|
|
return deltimer(t)
|
|
}
|
|
|
|
// Go runtime.
|
|
|
|
// Ready the goroutine arg.
|
|
func goroutineReady(arg interface{}, seq uintptr) {
|
|
goready(arg.(*g), 0)
|
|
}
|
|
|
|
func addtimer(t *timer) {
|
|
tb := t.assignBucket()
|
|
lock(&tb.lock)
|
|
tb.addtimerLocked(t)
|
|
unlock(&tb.lock)
|
|
}
|
|
|
|
// Add a timer to the heap and start or kick timerproc if the new timer is
|
|
// earlier than any of the others.
|
|
// Timers are locked.
|
|
func (tb *timersBucket) addtimerLocked(t *timer) {
|
|
// when must never be negative; otherwise timerproc will overflow
|
|
// during its delta calculation and never expire other runtime timers.
|
|
if t.when < 0 {
|
|
t.when = 1<<63 - 1
|
|
}
|
|
t.i = len(tb.t)
|
|
tb.t = append(tb.t, t)
|
|
siftupTimer(tb.t, t.i)
|
|
if t.i == 0 {
|
|
// siftup moved to top: new earliest deadline.
|
|
if tb.sleeping {
|
|
tb.sleeping = false
|
|
notewakeup(&tb.waitnote)
|
|
}
|
|
if tb.rescheduling {
|
|
tb.rescheduling = false
|
|
goready(tb.gp, 0)
|
|
}
|
|
}
|
|
if !tb.created {
|
|
tb.created = true
|
|
go timerproc(tb)
|
|
}
|
|
}
|
|
|
|
// Delete timer t from the heap.
|
|
// Do not need to update the timerproc: if it wakes up early, no big deal.
|
|
func deltimer(t *timer) bool {
|
|
if t.tb == nil {
|
|
// t.tb can be nil if the user created a timer
|
|
// directly, without invoking startTimer e.g
|
|
// time.Ticker{C: c}
|
|
// In this case, return early without any deletion.
|
|
// See Issue 21874.
|
|
return false
|
|
}
|
|
|
|
tb := t.tb
|
|
|
|
lock(&tb.lock)
|
|
// t may not be registered anymore and may have
|
|
// a bogus i (typically 0, if generated by Go).
|
|
// Verify it before proceeding.
|
|
i := t.i
|
|
last := len(tb.t) - 1
|
|
if i < 0 || i > last || tb.t[i] != t {
|
|
unlock(&tb.lock)
|
|
return false
|
|
}
|
|
if i != last {
|
|
tb.t[i] = tb.t[last]
|
|
tb.t[i].i = i
|
|
}
|
|
tb.t[last] = nil
|
|
tb.t = tb.t[:last]
|
|
if i != last {
|
|
siftupTimer(tb.t, i)
|
|
siftdownTimer(tb.t, i)
|
|
}
|
|
unlock(&tb.lock)
|
|
return true
|
|
}
|
|
|
|
// Timerproc runs the time-driven events.
|
|
// It sleeps until the next event in the tb heap.
|
|
// If addtimer inserts a new earlier event, it wakes timerproc early.
|
|
func timerproc(tb *timersBucket) {
|
|
tb.gp = getg()
|
|
for {
|
|
lock(&tb.lock)
|
|
tb.sleeping = false
|
|
now := nanotime()
|
|
delta := int64(-1)
|
|
for {
|
|
if len(tb.t) == 0 {
|
|
delta = -1
|
|
break
|
|
}
|
|
t := tb.t[0]
|
|
delta = t.when - now
|
|
if delta > 0 {
|
|
break
|
|
}
|
|
if t.period > 0 {
|
|
// leave in heap but adjust next time to fire
|
|
t.when += t.period * (1 + -delta/t.period)
|
|
siftdownTimer(tb.t, 0)
|
|
} else {
|
|
// remove from heap
|
|
last := len(tb.t) - 1
|
|
if last > 0 {
|
|
tb.t[0] = tb.t[last]
|
|
tb.t[0].i = 0
|
|
}
|
|
tb.t[last] = nil
|
|
tb.t = tb.t[:last]
|
|
if last > 0 {
|
|
siftdownTimer(tb.t, 0)
|
|
}
|
|
t.i = -1 // mark as removed
|
|
}
|
|
f := t.f
|
|
arg := t.arg
|
|
seq := t.seq
|
|
unlock(&tb.lock)
|
|
if raceenabled {
|
|
raceacquire(unsafe.Pointer(t))
|
|
}
|
|
f(arg, seq)
|
|
lock(&tb.lock)
|
|
}
|
|
if delta < 0 || faketime > 0 {
|
|
// No timers left - put goroutine to sleep.
|
|
tb.rescheduling = true
|
|
goparkunlock(&tb.lock, "timer goroutine (idle)", traceEvGoBlock, 1)
|
|
continue
|
|
}
|
|
// At least one timer pending. Sleep until then.
|
|
tb.sleeping = true
|
|
tb.sleepUntil = now + delta
|
|
noteclear(&tb.waitnote)
|
|
unlock(&tb.lock)
|
|
notetsleepg(&tb.waitnote, delta)
|
|
}
|
|
}
|
|
|
|
func timejump() *g {
|
|
if faketime == 0 {
|
|
return nil
|
|
}
|
|
|
|
for i := range timers {
|
|
lock(&timers[i].lock)
|
|
}
|
|
gp := timejumpLocked()
|
|
for i := range timers {
|
|
unlock(&timers[i].lock)
|
|
}
|
|
|
|
return gp
|
|
}
|
|
|
|
func timejumpLocked() *g {
|
|
// Determine a timer bucket with minimum when.
|
|
var minT *timer
|
|
for i := range timers {
|
|
tb := &timers[i]
|
|
if !tb.created || len(tb.t) == 0 {
|
|
continue
|
|
}
|
|
t := tb.t[0]
|
|
if minT == nil || t.when < minT.when {
|
|
minT = t
|
|
}
|
|
}
|
|
if minT == nil || minT.when <= faketime {
|
|
return nil
|
|
}
|
|
|
|
faketime = minT.when
|
|
tb := minT.tb
|
|
if !tb.rescheduling {
|
|
return nil
|
|
}
|
|
tb.rescheduling = false
|
|
return tb.gp
|
|
}
|
|
|
|
func timeSleepUntil() int64 {
|
|
next := int64(1<<63 - 1)
|
|
|
|
// Determine minimum sleepUntil across all the timer buckets.
|
|
//
|
|
// The function can not return a precise answer,
|
|
// as another timer may pop in as soon as timers have been unlocked.
|
|
// So lock the timers one by one instead of all at once.
|
|
for i := range timers {
|
|
tb := &timers[i]
|
|
|
|
lock(&tb.lock)
|
|
if tb.sleeping && tb.sleepUntil < next {
|
|
next = tb.sleepUntil
|
|
}
|
|
unlock(&tb.lock)
|
|
}
|
|
|
|
return next
|
|
}
|
|
|
|
// Heap maintenance algorithms.
|
|
|
|
func siftupTimer(t []*timer, i int) {
|
|
when := t[i].when
|
|
tmp := t[i]
|
|
for i > 0 {
|
|
p := (i - 1) / 4 // parent
|
|
if when >= t[p].when {
|
|
break
|
|
}
|
|
t[i] = t[p]
|
|
t[i].i = i
|
|
i = p
|
|
}
|
|
if tmp != t[i] {
|
|
t[i] = tmp
|
|
t[i].i = i
|
|
}
|
|
}
|
|
|
|
func siftdownTimer(t []*timer, i int) {
|
|
n := len(t)
|
|
when := t[i].when
|
|
tmp := t[i]
|
|
for {
|
|
c := i*4 + 1 // left child
|
|
c3 := c + 2 // mid child
|
|
if c >= n {
|
|
break
|
|
}
|
|
w := t[c].when
|
|
if c+1 < n && t[c+1].when < w {
|
|
w = t[c+1].when
|
|
c++
|
|
}
|
|
if c3 < n {
|
|
w3 := t[c3].when
|
|
if c3+1 < n && t[c3+1].when < w3 {
|
|
w3 = t[c3+1].when
|
|
c3++
|
|
}
|
|
if w3 < w {
|
|
w = w3
|
|
c = c3
|
|
}
|
|
}
|
|
if w >= when {
|
|
break
|
|
}
|
|
t[i] = t[c]
|
|
t[i].i = i
|
|
i = c
|
|
}
|
|
if tmp != t[i] {
|
|
t[i] = tmp
|
|
t[i].i = i
|
|
}
|
|
}
|
|
|
|
// Entry points for net, time to call nanotime.
|
|
|
|
//go:linkname poll_runtimeNano internal/poll.runtimeNano
|
|
func poll_runtimeNano() int64 {
|
|
return nanotime()
|
|
}
|
|
|
|
//go:linkname time_runtimeNano time.runtimeNano
|
|
func time_runtimeNano() int64 {
|
|
return nanotime()
|
|
}
|
|
|
|
// Monotonic times are reported as offsets from startNano.
|
|
// We initialize startNano to nanotime() - 1 so that on systems where
|
|
// monotonic time resolution is fairly low (e.g. Windows 2008
|
|
// which appears to have a default resolution of 15ms),
|
|
// we avoid ever reporting a nanotime of 0.
|
|
// (Callers may want to use 0 as "time not set".)
|
|
var startNano int64 = nanotime() - 1
|