mirror of
https://github.com/netbirdio/gvisor.git
synced 2026-05-22 17:12:49 -07:00
ktime: support varying Timer implementations
- Rename Timer to SampledTimer. - Move all Clock methods except Now to new interface SampledClock. - Move SampledTimer's exported methods (except SetClock) to new interface Timer. Combine Swap and SwapAnd into Set to reduce the number of redundant methods that must be implemented. - Add interface method Clock.NewTimer. This is in preparation for cl/693856539, which adds a second Timer implementation. PiperOrigin-RevId: 694299679
This commit is contained in:
@@ -92,7 +92,7 @@ func (*globalUniqueIDProvider) UniqueID() uint64 {
|
||||
// inotify cookies.
|
||||
var lastInotifyCookie atomicbitops.Uint32
|
||||
|
||||
// hostClock implements ktime.Clock.
|
||||
// hostClock implements ktime.SampledClock.
|
||||
type hostClock struct {
|
||||
ktime.WallRateClock
|
||||
ktime.NoClockEvents
|
||||
@@ -103,6 +103,16 @@ func (*hostClock) Now() ktime.Time {
|
||||
return ktime.FromNanoseconds(time.Now().UnixNano())
|
||||
}
|
||||
|
||||
// SupportsTimers implements ktime.Clock.Now.
|
||||
func (*hostClock) SupportsTimers() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// NewTimer implements ktime.Clock.NewTimer.
|
||||
func (c *hostClock) NewTimer(l ktime.Listener) ktime.Timer {
|
||||
return ktime.NewSampledTimer(c, l)
|
||||
}
|
||||
|
||||
// RegisterValue registers additional values with this test context. Useful for
|
||||
// providing values from external packages that contexttest can't depend on.
|
||||
func (t *TestContext) RegisterValue(key, value any) {
|
||||
|
||||
@@ -37,7 +37,7 @@ type TimerFileDescription struct {
|
||||
vfs.NoLockFD
|
||||
|
||||
events waiter.Queue
|
||||
timer *ktime.Timer
|
||||
timer ktime.Timer
|
||||
|
||||
// val is the number of timer expirations since the last successful
|
||||
// call to PRead, or SetTime. val must be accessed using atomic memory
|
||||
@@ -53,7 +53,7 @@ func New(ctx context.Context, vfsObj *vfs.VirtualFilesystem, clock ktime.Clock,
|
||||
vd := vfsObj.NewAnonVirtualDentry("[timerfd]")
|
||||
defer vd.DecRef(ctx)
|
||||
tfd := &TimerFileDescription{}
|
||||
tfd.timer = ktime.NewTimer(clock, tfd)
|
||||
tfd.timer = clock.NewTimer(tfd)
|
||||
if err := tfd.vfsfd.Init(tfd, flags, vd.Mount(), vd.Dentry(), &vfs.FileDescriptionOptions{
|
||||
UseDentryMetadata: true,
|
||||
DenyPRead: true,
|
||||
@@ -98,7 +98,7 @@ func (tfd *TimerFileDescription) GetTime() (ktime.Time, ktime.Setting) {
|
||||
// of expirations to 0, and returns the previous setting and the time at which
|
||||
// it was observed.
|
||||
func (tfd *TimerFileDescription) SetTime(s ktime.Setting) (ktime.Time, ktime.Setting) {
|
||||
return tfd.timer.SwapAnd(s, func() { tfd.val.Store(0) })
|
||||
return tfd.timer.Set(s, func() { tfd.val.Store(0) })
|
||||
}
|
||||
|
||||
// Readiness implements waiter.Waitable.Readiness.
|
||||
|
||||
@@ -1634,12 +1634,12 @@ func (k *Kernel) ApplicationCores() uint {
|
||||
}
|
||||
|
||||
// RealtimeClock returns the application CLOCK_REALTIME clock.
|
||||
func (k *Kernel) RealtimeClock() ktime.Clock {
|
||||
func (k *Kernel) RealtimeClock() ktime.SampledClock {
|
||||
return k.timekeeper.realtimeClock
|
||||
}
|
||||
|
||||
// MonotonicClock returns the application CLOCK_MONOTONIC clock.
|
||||
func (k *Kernel) MonotonicClock() ktime.Clock {
|
||||
func (k *Kernel) MonotonicClock() ktime.SampledClock {
|
||||
return k.timekeeper.monotonicClock
|
||||
}
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@ import (
|
||||
//
|
||||
// +stateify savable
|
||||
type IntervalTimer struct {
|
||||
timer *ktime.Timer
|
||||
timer ktime.Timer
|
||||
|
||||
// If target is not nil, it receives signo from timer expirations. If group
|
||||
// is true, these signals are thread-group-directed. These fields are
|
||||
@@ -215,7 +215,7 @@ func (t *Task) IntervalTimerCreate(c ktime.Clock, sigev *linux.Sigevent) (linux.
|
||||
return 0, linuxerr.EINVAL
|
||||
}
|
||||
}
|
||||
it.timer = ktime.NewTimer(c, it)
|
||||
it.timer = c.NewTimer(it)
|
||||
|
||||
t.tg.timers[id] = it
|
||||
return id, nil
|
||||
@@ -247,7 +247,7 @@ func (t *Task) IntervalTimerSettime(id linux.TimerID, its linux.Itimerspec, abs
|
||||
if err != nil {
|
||||
return linux.Itimerspec{}, err
|
||||
}
|
||||
tm, oldS := it.timer.SwapAnd(newS, it.timerSettingChanged)
|
||||
tm, oldS := it.timer.Set(newS, it.timerSettingChanged)
|
||||
its = ktime.ItimerspecFromSetting(tm, oldS)
|
||||
return its, nil
|
||||
}
|
||||
|
||||
@@ -560,12 +560,14 @@ type Task struct {
|
||||
// copyScratchBuffer is exclusive to the task goroutine.
|
||||
copyScratchBuffer [copyScratchBufferLen]byte `state:"nosave"`
|
||||
|
||||
// blockingTimer is used for blocking timeouts. blockingTimerChan is the
|
||||
// channel that is sent to when blockingTimer fires.
|
||||
// blockingTimer is used for blocking timeouts from ktime.SampledClocks.
|
||||
// blockingTimerListener sends to blockingTimerChan when blockingTimer
|
||||
// expires.
|
||||
//
|
||||
// blockingTimer is exclusive to the task goroutine.
|
||||
blockingTimer *ktime.Timer `state:"nosave"`
|
||||
blockingTimerChan <-chan struct{} `state:"nosave"`
|
||||
blockingTimer *ktime.SampledTimer `state:"nosave"`
|
||||
blockingTimerListener ktime.Listener `state:"nosave"`
|
||||
blockingTimerChan <-chan struct{} `state:"nosave"`
|
||||
|
||||
// futexWaiter is used for futex(FUTEX_WAIT) syscalls.
|
||||
//
|
||||
|
||||
@@ -65,7 +65,7 @@ func (t *Task) Setitimer(id int32, newitv linux.ItimerVal) (linux.ItimerVal, err
|
||||
if err != nil {
|
||||
return linux.ItimerVal{}, err
|
||||
}
|
||||
tm, olds = t.tg.itimerRealTimer.Swap(news)
|
||||
tm, olds = t.tg.itimerRealTimer.Set(news, nil)
|
||||
case linux.ITIMER_VIRTUAL:
|
||||
c := t.tg.UserCPUClock()
|
||||
t.k.cpuClockMu.Lock()
|
||||
|
||||
@@ -44,7 +44,7 @@ func (t *Task) BlockWithTimeout(C chan struct{}, haveTimeout bool, timeout time.
|
||||
clock := t.Kernel().MonotonicClock()
|
||||
start := clock.Now()
|
||||
deadline := start.Add(timeout)
|
||||
err := t.BlockWithDeadlineFrom(C, clock, true, deadline)
|
||||
err := t.blockWithDeadlineFromSampledClock(C, clock, deadline)
|
||||
|
||||
// Timeout, explicitly return a remaining duration of 0.
|
||||
if linuxerr.Equals(linuxerr.ETIMEDOUT, err) {
|
||||
@@ -81,7 +81,10 @@ func (t *Task) BlockWithTimeoutOn(w waiter.Waitable, mask waiter.EventMask, time
|
||||
//
|
||||
// Preconditions: The caller must be running on the task goroutine.
|
||||
func (t *Task) BlockWithDeadline(C <-chan struct{}, haveDeadline bool, deadline ktime.Time) error {
|
||||
return t.BlockWithDeadlineFrom(C, t.Kernel().MonotonicClock(), haveDeadline, deadline)
|
||||
if !haveDeadline {
|
||||
return t.block(C, nil)
|
||||
}
|
||||
return t.blockWithDeadlineFromSampledClock(C, t.Kernel().MonotonicClock(), deadline)
|
||||
}
|
||||
|
||||
// BlockWithDeadlineFrom is similar to BlockWithDeadline, except it uses the
|
||||
@@ -95,6 +98,33 @@ func (t *Task) BlockWithDeadlineFrom(C <-chan struct{}, clock ktime.Clock, haveD
|
||||
return t.block(C, nil)
|
||||
}
|
||||
|
||||
if c, ok := clock.(ktime.SampledClock); ok {
|
||||
return t.blockWithDeadlineFromSampledClock(C, c, deadline)
|
||||
}
|
||||
|
||||
// Start the timeout timer.
|
||||
timer := clock.NewTimer(t.blockingTimerListener)
|
||||
defer timer.Destroy()
|
||||
timer.Set(ktime.Setting{
|
||||
Enabled: true,
|
||||
Next: deadline,
|
||||
}, nil)
|
||||
|
||||
err := t.block(C, t.blockingTimerChan)
|
||||
|
||||
// Stop the timeout timer and drain the channel. If s.Enabled is true, the
|
||||
// timer didn't fire yet, so t.blockingTimerChan must be empty.
|
||||
if _, s := timer.Set(ktime.Setting{}, nil); !s.Enabled {
|
||||
select {
|
||||
case <-t.blockingTimerChan:
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (t *Task) blockWithDeadlineFromSampledClock(C <-chan struct{}, clock ktime.SampledClock, deadline ktime.Time) error {
|
||||
// Start the timeout timer.
|
||||
t.blockingTimer.SetClock(clock, ktime.Setting{
|
||||
Enabled: true,
|
||||
@@ -103,11 +133,13 @@ func (t *Task) BlockWithDeadlineFrom(C <-chan struct{}, clock ktime.Clock, haveD
|
||||
|
||||
err := t.block(C, t.blockingTimerChan)
|
||||
|
||||
// Stop the timeout timer and drain the channel.
|
||||
t.blockingTimer.Swap(ktime.Setting{})
|
||||
select {
|
||||
case <-t.blockingTimerChan:
|
||||
default:
|
||||
// Stop the timeout timer and drain the channel. If s.Enabled is true, the
|
||||
// timer didn't fire yet, so t.blockingTimerChan must be empty.
|
||||
if _, s := t.blockingTimer.Set(ktime.Setting{}, nil); !s.Enabled {
|
||||
select {
|
||||
case <-t.blockingTimerChan:
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
return err
|
||||
|
||||
@@ -65,10 +65,9 @@ func (t *Task) run(threadID uintptr) {
|
||||
// Construct t.blockingTimer here. We do this here because we can't
|
||||
// reconstruct t.blockingTimer during restore in Task.afterLoad(), because
|
||||
// kernel.timekeeper.SetClocks() hasn't been called yet.
|
||||
blockingTimerNotifier, blockingTimerChan := ktime.NewChannelNotifier()
|
||||
t.blockingTimer = ktime.NewTimer(t.k.MonotonicClock(), blockingTimerNotifier)
|
||||
t.blockingTimerListener, t.blockingTimerChan = ktime.NewChannelNotifier()
|
||||
t.blockingTimer = ktime.NewSampledTimer(t.k.MonotonicClock(), t.blockingTimerListener)
|
||||
defer t.blockingTimer.Destroy()
|
||||
t.blockingTimerChan = blockingTimerChan
|
||||
|
||||
// Activate our address space.
|
||||
t.Activate()
|
||||
|
||||
@@ -271,6 +271,11 @@ func (tc *taskClock) Now() ktime.Time {
|
||||
return ktime.FromNanoseconds(stats.UserTime.Nanoseconds())
|
||||
}
|
||||
|
||||
// NewTimer implements ktime.Clock.NewTimer.
|
||||
func (tc *taskClock) NewTimer(l ktime.Listener) ktime.Timer {
|
||||
return ktime.NewSampledTimer(tc, l)
|
||||
}
|
||||
|
||||
// tgClock is a ktime.Clock that measures the time a thread group has spent
|
||||
// executing. tgClock is primarily used to implement CLOCK_PROCESS_CPUTIME_ID.
|
||||
//
|
||||
@@ -296,6 +301,11 @@ func (tgc *tgClock) Now() ktime.Time {
|
||||
return ktime.FromNanoseconds(stats.UserTime.Nanoseconds())
|
||||
}
|
||||
|
||||
// NewTimer implements ktime.Clock.NewTimer.
|
||||
func (tgc *tgClock) NewTimer(l ktime.Listener) ktime.Timer {
|
||||
return ktime.NewSampledTimer(tgc, l)
|
||||
}
|
||||
|
||||
// WallTimeUntil implements ktime.Clock.WallTimeUntil.
|
||||
func (tgc *tgClock) WallTimeUntil(t, now ktime.Time) time.Duration {
|
||||
// Thread group CPU time should not exceed wall time * live tasks, since
|
||||
|
||||
@@ -171,7 +171,7 @@ type ThreadGroup struct {
|
||||
timerMu threadGroupTimerMutex `state:"nosave"`
|
||||
|
||||
// itimerRealTimer implements ITIMER_REAL for the thread group.
|
||||
itimerRealTimer *ktime.Timer
|
||||
itimerRealTimer *ktime.SampledTimer
|
||||
|
||||
// itimerVirtSetting is the ITIMER_VIRTUAL setting for the thread group.
|
||||
//
|
||||
@@ -295,7 +295,7 @@ func (k *Kernel) NewThreadGroup(pidns *PIDNamespace, sh *SignalHandlers, termina
|
||||
ioUsage: &usage.IO{},
|
||||
limits: limits,
|
||||
}
|
||||
tg.itimerRealTimer = ktime.NewTimer(k.timekeeper.monotonicClock, &itimerRealListener{tg: tg})
|
||||
tg.itimerRealTimer = ktime.NewSampledTimer(k.timekeeper.monotonicClock, &itimerRealListener{tg: tg})
|
||||
tg.timers = make(map[linux.TimerID]*IntervalTimer)
|
||||
tg.oldRSeqCritical.Store(&OldRSeqCriticalRegion{})
|
||||
return tg
|
||||
|
||||
@@ -325,7 +325,7 @@ func (t *Timekeeper) BootTime() ktime.Time {
|
||||
return t.bootTime
|
||||
}
|
||||
|
||||
// timekeeperClock is a ktime.Clock that reads time from a
|
||||
// timekeeperClock is a ktime.SampledClock that reads time from a
|
||||
// kernel.Timekeeper-managed clock.
|
||||
//
|
||||
// +stateify savable
|
||||
@@ -333,7 +333,7 @@ type timekeeperClock struct {
|
||||
tk *Timekeeper
|
||||
c sentrytime.ClockID
|
||||
|
||||
// Implements ktime.Clock.WallTimeUntil.
|
||||
// Implements ktime.SampledClock.WallTimeUntil.
|
||||
ktime.WallRateClock `state:"nosave"`
|
||||
|
||||
// Implements waiter.Waitable. (We have no ability to detect
|
||||
@@ -349,3 +349,8 @@ func (tc *timekeeperClock) Now() ktime.Time {
|
||||
}
|
||||
return ktime.FromNanoseconds(now)
|
||||
}
|
||||
|
||||
// NewTimer implements ktime.Clock.NewTimer.
|
||||
func (tc *timekeeperClock) NewTimer(l ktime.Listener) ktime.Timer {
|
||||
return ktime.NewSampledTimer(tc, l)
|
||||
}
|
||||
|
||||
@@ -7,13 +7,13 @@ package(
|
||||
)
|
||||
|
||||
go_template_instance(
|
||||
name = "seqatomic_clock",
|
||||
out = "seqatomic_clock_unsafe.go",
|
||||
name = "seqatomic_sampled_clock",
|
||||
out = "seqatomic_sampled_clock_unsafe.go",
|
||||
package = "ktime",
|
||||
suffix = "Clock",
|
||||
suffix = "SampledClock",
|
||||
template = "//pkg/sync/seqatomic:generic_seqatomic",
|
||||
types = {
|
||||
"Value": "Clock",
|
||||
"Value": "SampledClock",
|
||||
},
|
||||
)
|
||||
|
||||
@@ -22,7 +22,8 @@ go_library(
|
||||
srcs = [
|
||||
"context.go",
|
||||
"ktime.go",
|
||||
"seqatomic_clock_unsafe.go",
|
||||
"sampled_timer.go",
|
||||
"seqatomic_sampled_clock_unsafe.go",
|
||||
"util.go",
|
||||
],
|
||||
visibility = ["//pkg/sentry:internal"],
|
||||
|
||||
+47
-378
@@ -12,8 +12,8 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// Package ktime defines the Timer type, which provides a periodic timer that
|
||||
// works by sampling a user-provided clock.
|
||||
// Package ktime provides an API for clocks and timers implemented by the
|
||||
// sentry.
|
||||
package ktime
|
||||
|
||||
import (
|
||||
@@ -23,19 +23,6 @@ import (
|
||||
|
||||
"gvisor.dev/gvisor/pkg/abi/linux"
|
||||
"gvisor.dev/gvisor/pkg/errors/linuxerr"
|
||||
"gvisor.dev/gvisor/pkg/sync"
|
||||
"gvisor.dev/gvisor/pkg/waiter"
|
||||
)
|
||||
|
||||
// Events that may be generated by a Clock.
|
||||
const (
|
||||
// ClockEventSet occurs when a Clock undergoes a discontinuous change.
|
||||
ClockEventSet waiter.EventMask = 1 << iota
|
||||
|
||||
// ClockEventRateIncrease occurs when the rate at which a Clock advances
|
||||
// increases significantly, such that values returned by previous calls to
|
||||
// Clock.WallTimeUntil may be too large.
|
||||
ClockEventRateIncrease
|
||||
)
|
||||
|
||||
// Time represents an instant in time with nanosecond precision.
|
||||
@@ -221,73 +208,53 @@ type Clock interface {
|
||||
// Now returns the current time in nanoseconds according to the Clock.
|
||||
Now() Time
|
||||
|
||||
// WallTimeUntil returns the estimated wall time until Now will return a
|
||||
// value greater than or equal to t, given that a recent call to Now
|
||||
// returned now. If t has already passed, WallTimeUntil may return 0 or a
|
||||
// negative value.
|
||||
// NewTimer returns a Timer whose time source is the Clock, which sends
|
||||
// expirations to the given Listener. The Timer is initially stopped
|
||||
// and has no first expiration or period configured.
|
||||
NewTimer(Listener) Timer
|
||||
}
|
||||
|
||||
// Timer is an optionally-periodic timer. Timer's semantics support the
|
||||
// requirements of Linux's interval timers (setitimer(2), timer_create(2),
|
||||
// timerfd_create(2)).
|
||||
type Timer interface {
|
||||
// Destroy releases resources owned by the Timer. Pause and Resume may be
|
||||
// called on a destroyed Timer and are no-ops. No other methods may be
|
||||
// called on a destroyed Timer.
|
||||
Destroy()
|
||||
|
||||
// Pause pauses the Timer, ensuring that it does not generate any further
|
||||
// expirations until Resume is called. If the Timer is already paused,
|
||||
// Pause has no effect.
|
||||
//
|
||||
// WallTimeUntil must be abstract to support Clocks that do not represent
|
||||
// wall time (e.g. thread group execution timers). Clocks that represent
|
||||
// wall times may embed the WallRateClock type to obtain an appropriate
|
||||
// trivial implementation of WallTimeUntil.
|
||||
// Pause and Resume are used to pause Timers during sentry checkpointing;
|
||||
// non-checkpoint/restore code should not call these functions.
|
||||
Pause()
|
||||
|
||||
// Resume ends the effect of Pause. If the Timer is not paused, Resume has
|
||||
// no effect.
|
||||
Resume()
|
||||
|
||||
// Clock returns the Timer's time source.
|
||||
Clock() Clock
|
||||
|
||||
// Get returns a snapshot of the Timer's current Setting and the time
|
||||
// (according to the Timer's Clock) at which the snapshot was taken.
|
||||
//
|
||||
// WallTimeUntil is used to determine when associated Timers should next
|
||||
// check for expirations. Returning too small a value may result in
|
||||
// spurious Timer goroutine wakeups, while returning too large a value may
|
||||
// result in late expirations. Implementations should usually err on the
|
||||
// side of underestimating.
|
||||
WallTimeUntil(t, now Time) time.Duration
|
||||
// Preconditions: The Timer must not be paused (since its Setting cannot be
|
||||
// advanced to the current time while it is paused.)
|
||||
Get() (Time, Setting)
|
||||
|
||||
// Waitable methods may be used to subscribe to Clock events. Waiters will
|
||||
// not be preserved by Save and must be re-established during restore.
|
||||
// Set atomically changes the Timer's Setting, calls f if it is not nil,
|
||||
// and returns the Timer's previous Setting and the time (according to the
|
||||
// Timer's Clock) at which the snapshot was taken. Setting s.Enabled to
|
||||
// true starts the Timer, while setting s.Enabled to false stops it.
|
||||
//
|
||||
// Since Clock events are transient, implementations of
|
||||
// waiter.Waitable.Readiness should return 0.
|
||||
waiter.Waitable
|
||||
}
|
||||
|
||||
// WallRateClock implements Clock.WallTimeUntil for Clocks that elapse at the
|
||||
// same rate as wall time.
|
||||
type WallRateClock struct{}
|
||||
|
||||
// WallTimeUntil implements Clock.WallTimeUntil.
|
||||
func (*WallRateClock) WallTimeUntil(t, now Time) time.Duration {
|
||||
return t.Sub(now)
|
||||
}
|
||||
|
||||
// NoClockEvents implements waiter.Waitable for Clocks that do not generate
|
||||
// events.
|
||||
type NoClockEvents struct{}
|
||||
|
||||
// Readiness implements waiter.Waitable.Readiness.
|
||||
func (*NoClockEvents) Readiness(mask waiter.EventMask) waiter.EventMask {
|
||||
return 0
|
||||
}
|
||||
|
||||
// EventRegister implements waiter.Waitable.EventRegister.
|
||||
func (*NoClockEvents) EventRegister(e *waiter.Entry) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// EventUnregister implements waiter.Waitable.EventUnregister.
|
||||
func (*NoClockEvents) EventUnregister(e *waiter.Entry) {
|
||||
}
|
||||
|
||||
// ClockEventsQueue implements waiter.Waitable by wrapping waiter.Queue and
|
||||
// defining waiter.Waitable.Readiness as required by Clock.
|
||||
type ClockEventsQueue struct {
|
||||
waiter.Queue
|
||||
}
|
||||
|
||||
// EventRegister implements waiter.Waitable.
|
||||
func (c *ClockEventsQueue) EventRegister(e *waiter.Entry) error {
|
||||
c.Queue.EventRegister(e)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Readiness implements waiter.Waitable.Readiness.
|
||||
func (*ClockEventsQueue) Readiness(mask waiter.EventMask) waiter.EventMask {
|
||||
return 0
|
||||
// Preconditions:
|
||||
// - The Timer must not be paused.
|
||||
// - f cannot call any Timer methods or take any locks preceding Timer
|
||||
// methods in the lock order.
|
||||
Set(s Setting, f func()) (Time, Setting)
|
||||
}
|
||||
|
||||
// Listener receives expirations from a Timer.
|
||||
@@ -295,8 +262,8 @@ type Listener interface {
|
||||
// NotifyTimer is called when its associated Timer expires. exp is the number
|
||||
// of expirations. setting is the next timer Setting.
|
||||
//
|
||||
// Notify is called with the associated Timer's mutex locked, so Notify
|
||||
// must not take any locks that precede Timer.mu in lock order.
|
||||
// NotifyTimer cannot call any Timer methods or take any locks preceding
|
||||
// the Timer in the lock order.
|
||||
//
|
||||
// Preconditions: exp > 0.
|
||||
NotifyTimer(exp uint64)
|
||||
@@ -409,310 +376,12 @@ func (s Setting) At(now Time) (Setting, uint64) {
|
||||
return s, exp
|
||||
}
|
||||
|
||||
// Timer is an optionally-periodic timer driven by sampling a user-specified
|
||||
// Clock. Timer's semantics support the requirements of Linux's interval timers
|
||||
// (setitimer(2), timer_create(2), timerfd_create(2)).
|
||||
//
|
||||
// Timers should be created using NewTimer and must be cleaned up by calling
|
||||
// Timer.Destroy when no longer used.
|
||||
//
|
||||
// +stateify savable
|
||||
type Timer struct {
|
||||
// clock is the time source. clock is protected by mu and clockSeq.
|
||||
clockSeq sync.SeqCount `state:"nosave"`
|
||||
clock Clock
|
||||
|
||||
// listener is notified of expirations. listener is immutable.
|
||||
listener Listener
|
||||
|
||||
// mu protects the following mutable fields.
|
||||
mu sync.Mutex `state:"nosave"`
|
||||
|
||||
// setting is the timer setting. setting is protected by mu.
|
||||
setting Setting
|
||||
|
||||
pauseState timerPauseState
|
||||
|
||||
// kicker is used to wake the Timer goroutine. The kicker pointer is
|
||||
// immutable, but its state is protected by mu.
|
||||
kicker *time.Timer `state:"nosave"`
|
||||
|
||||
// entry is registered with clock.EventRegister. entry is immutable.
|
||||
//
|
||||
// Per comment in Clock, entry must be re-registered after restore; per
|
||||
// comment in Timer.Load, this is done in Timer.Resume.
|
||||
entry waiter.Entry `state:"nosave"`
|
||||
|
||||
// events is the channel that will be notified whenever entry receives an
|
||||
// event. It is also closed by Timer.Destroy to instruct the Timer
|
||||
// goroutine to exit.
|
||||
events chan struct{} `state:"nosave"`
|
||||
}
|
||||
|
||||
type timerPauseState uint8
|
||||
|
||||
const (
|
||||
// timerUnpaused indicates that the Timer is neither paused nor
|
||||
// destroyed.
|
||||
timerUnpaused timerPauseState = iota
|
||||
|
||||
// timerPaused indicates that the Timer is paused, not destroyed.
|
||||
timerPaused
|
||||
|
||||
// timerDestroyed indicates that the Timer is destroyed.
|
||||
timerDestroyed
|
||||
)
|
||||
|
||||
// timerTickEvents are Clock events that require the Timer goroutine to Tick
|
||||
// prematurely.
|
||||
const timerTickEvents = ClockEventSet | ClockEventRateIncrease
|
||||
|
||||
// NewTimer returns a new Timer that will obtain time from clock and send
|
||||
// expirations to listener. The Timer is initially stopped and has no first
|
||||
// expiration or period configured.
|
||||
func NewTimer(clock Clock, listener Listener) *Timer {
|
||||
t := &Timer{
|
||||
clock: clock,
|
||||
listener: listener,
|
||||
}
|
||||
t.init()
|
||||
return t
|
||||
}
|
||||
|
||||
// init initializes Timer state that is not preserved across save/restore. If
|
||||
// init has already been called, calling it again is a no-op.
|
||||
//
|
||||
// Preconditions: t.mu must be locked, or the caller must have exclusive access
|
||||
// to t.
|
||||
func (t *Timer) init() {
|
||||
if t.kicker != nil {
|
||||
return
|
||||
}
|
||||
// If t.kicker is nil, the Timer goroutine can't be running, so we can't
|
||||
// race with it.
|
||||
t.kicker = time.NewTimer(0)
|
||||
t.entry, t.events = waiter.NewChannelEntry(timerTickEvents)
|
||||
if err := t.clock.EventRegister(&t.entry); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
go t.runGoroutine() // S/R-SAFE: synchronized by t.mu
|
||||
}
|
||||
|
||||
// Destroy releases resources owned by the Timer. Pause and Resume may be
|
||||
// called on a Destroyed Timer and are no-ops. No other methods may be called
|
||||
// on a Destroyed Timer.
|
||||
func (t *Timer) Destroy() {
|
||||
// Stop the Timer, ensuring that the Timer goroutine will not call
|
||||
// t.kicker.Reset, before calling t.kicker.Stop.
|
||||
t.mu.Lock()
|
||||
t.setting.Enabled = false
|
||||
// Set timerDestroyed to prevent t.Tick() from mutating Timer state.
|
||||
t.pauseState = timerDestroyed
|
||||
t.mu.Unlock()
|
||||
t.kicker.Stop()
|
||||
// Unregister t.entry, ensuring that the Clock will not send to t.events,
|
||||
// before closing t.events to instruct the Timer goroutine to exit.
|
||||
t.clock.EventUnregister(&t.entry)
|
||||
close(t.events)
|
||||
}
|
||||
|
||||
func (t *Timer) runGoroutine() {
|
||||
for {
|
||||
select {
|
||||
case <-t.kicker.C:
|
||||
case _, ok := <-t.events:
|
||||
if !ok {
|
||||
// Channel closed by Destroy.
|
||||
return
|
||||
}
|
||||
}
|
||||
t.Tick()
|
||||
}
|
||||
}
|
||||
|
||||
// Tick requests that the Timer immediately check for expirations and
|
||||
// re-evaluate when it should next check for expirations.
|
||||
func (t *Timer) Tick() {
|
||||
// Optimistically read t.Clock().Now() before locking t.mu, as t.clock is
|
||||
// unlikely to change.
|
||||
unlockedClock := t.Clock()
|
||||
now := unlockedClock.Now()
|
||||
t.mu.Lock()
|
||||
defer t.mu.Unlock()
|
||||
if t.pauseState != timerUnpaused {
|
||||
return
|
||||
}
|
||||
if t.clock != unlockedClock {
|
||||
now = t.clock.Now()
|
||||
}
|
||||
s, exp := t.setting.At(now)
|
||||
t.setting = s
|
||||
if exp > 0 {
|
||||
t.listener.NotifyTimer(exp)
|
||||
}
|
||||
t.resetKickerLocked(now)
|
||||
}
|
||||
|
||||
// Pause pauses the Timer, ensuring that it does not generate any further
|
||||
// expirations until Resume is called. If the Timer is already paused, Pause
|
||||
// has no effect.
|
||||
func (t *Timer) Pause() {
|
||||
t.mu.Lock()
|
||||
defer t.mu.Unlock()
|
||||
if t.pauseState != timerUnpaused {
|
||||
return
|
||||
}
|
||||
t.pauseState = timerPaused
|
||||
// t.kicker may be nil if we were restored but never resumed.
|
||||
if t.kicker != nil {
|
||||
t.kicker.Stop()
|
||||
}
|
||||
}
|
||||
|
||||
// Resume ends the effect of Pause. If the Timer is not paused, Resume has no
|
||||
// effect.
|
||||
func (t *Timer) Resume() {
|
||||
t.mu.Lock()
|
||||
defer t.mu.Unlock()
|
||||
if t.pauseState != timerPaused {
|
||||
return
|
||||
}
|
||||
t.pauseState = timerUnpaused
|
||||
|
||||
// Lazily initialize the Timer. We can't call Timer.init until Timer.Resume
|
||||
// because save/restore will restore Timers before
|
||||
// kernel.Timekeeper.SetClocks() has been called, so if t.clock is backed
|
||||
// by a kernel.Timekeeper then the Timer goroutine will panic if it calls
|
||||
// t.clock.Now().
|
||||
t.init()
|
||||
|
||||
// Kick the Timer goroutine in case it was already initialized, but the
|
||||
// Timer goroutine was sleeping.
|
||||
t.kicker.Reset(0)
|
||||
}
|
||||
|
||||
// Get returns a snapshot of the Timer's current Setting and the time
|
||||
// (according to the Timer's Clock) at which the snapshot was taken.
|
||||
//
|
||||
// Preconditions: The Timer must not be paused (since its Setting cannot
|
||||
// be advanced to the current time while it is paused.)
|
||||
func (t *Timer) Get() (Time, Setting) {
|
||||
// Optimistically read t.Clock().Now() before locking t.mu, as t.clock is
|
||||
// unlikely to change.
|
||||
unlockedClock := t.Clock()
|
||||
now := unlockedClock.Now()
|
||||
t.mu.Lock()
|
||||
defer t.mu.Unlock()
|
||||
if t.pauseState != timerUnpaused {
|
||||
panic(fmt.Sprintf("Timer.Get called on Timer %p in pause state %v", t, t.pauseState))
|
||||
}
|
||||
if t.clock != unlockedClock {
|
||||
now = t.clock.Now()
|
||||
}
|
||||
s, exp := t.setting.At(now)
|
||||
t.setting = s
|
||||
if exp > 0 {
|
||||
t.listener.NotifyTimer(exp)
|
||||
}
|
||||
t.resetKickerLocked(now)
|
||||
return now, s
|
||||
}
|
||||
|
||||
// Swap atomically changes the Timer's Setting and returns the Timer's previous
|
||||
// Setting and the time (according to the Timer's Clock) at which the snapshot
|
||||
// was taken. Setting s.Enabled to true starts the Timer, while setting
|
||||
// s.Enabled to false stops it.
|
||||
//
|
||||
// Preconditions: The Timer must not be paused.
|
||||
func (t *Timer) Swap(s Setting) (Time, Setting) {
|
||||
return t.SwapAnd(s, nil)
|
||||
}
|
||||
|
||||
// SwapAnd atomically changes the Timer's Setting, calls f if it is not nil,
|
||||
// and returns the Timer's previous Setting and the time (according to the
|
||||
// Timer's Clock) at which the Setting was changed. Setting s.Enabled to true
|
||||
// starts the timer, while setting s.Enabled to false stops it.
|
||||
//
|
||||
// Preconditions:
|
||||
// - The Timer must not be paused.
|
||||
// - f cannot call any Timer methods since it is called with the Timer mutex
|
||||
// locked.
|
||||
func (t *Timer) SwapAnd(s Setting, f func()) (Time, Setting) {
|
||||
// Optimistically read t.Clock().Now() before locking t.mu, as t.clock is
|
||||
// unlikely to change.
|
||||
unlockedClock := t.Clock()
|
||||
now := unlockedClock.Now()
|
||||
t.mu.Lock()
|
||||
defer t.mu.Unlock()
|
||||
if t.pauseState != timerUnpaused {
|
||||
panic(fmt.Sprintf("Timer.SwapAnd called on Timer %p in pause state %v", t, t.pauseState))
|
||||
}
|
||||
if t.clock != unlockedClock {
|
||||
now = t.clock.Now()
|
||||
}
|
||||
oldS, oldExp := t.setting.At(now)
|
||||
if oldExp > 0 {
|
||||
t.listener.NotifyTimer(oldExp)
|
||||
}
|
||||
if f != nil {
|
||||
f()
|
||||
}
|
||||
newS, newExp := s.At(now)
|
||||
t.setting = newS
|
||||
if newExp > 0 {
|
||||
t.listener.NotifyTimer(newExp)
|
||||
}
|
||||
t.resetKickerLocked(now)
|
||||
return now, oldS
|
||||
}
|
||||
|
||||
// SetClock atomically changes a Timer's Clock and Setting.
|
||||
func (t *Timer) SetClock(c Clock, s Setting) {
|
||||
var now Time
|
||||
if s.Enabled {
|
||||
now = c.Now()
|
||||
}
|
||||
t.mu.Lock()
|
||||
defer t.mu.Unlock()
|
||||
t.setting = s
|
||||
if oldC := t.clock; oldC != c {
|
||||
oldC.EventUnregister(&t.entry)
|
||||
c.EventRegister(&t.entry)
|
||||
t.clockSeq.BeginWrite()
|
||||
t.clock = c
|
||||
t.clockSeq.EndWrite()
|
||||
}
|
||||
t.resetKickerLocked(now)
|
||||
}
|
||||
|
||||
// Preconditions: t.mu must be locked.
|
||||
func (t *Timer) resetKickerLocked(now Time) {
|
||||
if t.setting.Enabled {
|
||||
// Clock.WallTimeUntil may return a negative value. This is fine;
|
||||
// time.when treats negative Durations as 0.
|
||||
t.kicker.Reset(t.clock.WallTimeUntil(t.setting.Next, now))
|
||||
}
|
||||
// We don't call t.kicker.Stop if !t.setting.Enabled because in most cases
|
||||
// resetKickerLocked will be called from the Timer goroutine itself, in
|
||||
// which case t.kicker has already fired and t.kicker.Stop will be an
|
||||
// expensive no-op (time.Timer.Stop => time.stopTimer => runtime.stopTimer
|
||||
// => runtime.deltimer).
|
||||
}
|
||||
|
||||
// Clock returns the Clock used by t.
|
||||
func (t *Timer) Clock() Clock {
|
||||
return SeqAtomicLoadClock(&t.clockSeq, &t.clock)
|
||||
}
|
||||
|
||||
// ChannelNotifier is a Listener that sends on a channel.
|
||||
//
|
||||
// ChannelNotifier cannot be saved or loaded.
|
||||
type ChannelNotifier chan struct{}
|
||||
|
||||
// NewChannelNotifier creates a new channel notifier.
|
||||
//
|
||||
// If the notifier is used with a timer, Timer.Destroy will close the channel
|
||||
// returned here.
|
||||
func NewChannelNotifier() (Listener, <-chan struct{}) {
|
||||
tchan := make(chan struct{}, 1)
|
||||
return ChannelNotifier(tchan), tchan
|
||||
|
||||
@@ -0,0 +1,372 @@
|
||||
// Copyright 2024 The gVisor Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package ktime
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"gvisor.dev/gvisor/pkg/sync"
|
||||
"gvisor.dev/gvisor/pkg/waiter"
|
||||
)
|
||||
|
||||
// SampledTimer implements Timer using a goroutine that reads a SampledClock
|
||||
// whenever an expiration is expected to have occurred.
|
||||
//
|
||||
// +stateify savable
|
||||
type SampledTimer struct {
|
||||
// clock is the time source. clock is protected by mu and clockSeq.
|
||||
clockSeq sync.SeqCount `state:"nosave"`
|
||||
clock SampledClock
|
||||
|
||||
// listener is notified of expirations. listener is immutable.
|
||||
listener Listener
|
||||
|
||||
// mu protects the following mutable fields.
|
||||
mu sync.Mutex `state:"nosave"`
|
||||
|
||||
// setting is the timer setting. setting is protected by mu.
|
||||
setting Setting
|
||||
|
||||
pauseState timerPauseState
|
||||
|
||||
// kicker is used to wake the SampledTimer goroutine. The kicker pointer is
|
||||
// immutable, but its state is protected by mu.
|
||||
kicker *time.Timer `state:"nosave"`
|
||||
|
||||
// entry is registered with clock.EventRegister. entry is immutable.
|
||||
//
|
||||
// Per comment in SampledClock, entry must be re-registered after restore;
|
||||
// per comment in SampledTimer.Load, this is done in SampledTimer.Resume.
|
||||
entry waiter.Entry `state:"nosave"`
|
||||
|
||||
// events is the channel that will be notified whenever entry receives an
|
||||
// event. It is also closed by SampledTimer.Destroy to instruct the
|
||||
// goroutine to exit.
|
||||
events chan struct{} `state:"nosave"`
|
||||
}
|
||||
|
||||
type timerPauseState uint8
|
||||
|
||||
const (
|
||||
// timerUnpaused indicates that the SampledTimer is neither paused nor
|
||||
// destroyed.
|
||||
timerUnpaused timerPauseState = iota
|
||||
|
||||
// timerPaused indicates that the SampledTimer is paused, not destroyed.
|
||||
timerPaused
|
||||
|
||||
// timerDestroyed indicates that the SampledTimer is destroyed.
|
||||
timerDestroyed
|
||||
)
|
||||
|
||||
// NewSampledTimer returns a new SampledTimer consistent with the requirements
|
||||
// of Clock.NewTimer().
|
||||
func NewSampledTimer(clock SampledClock, listener Listener) *SampledTimer {
|
||||
t := &SampledTimer{
|
||||
clock: clock,
|
||||
listener: listener,
|
||||
}
|
||||
t.init()
|
||||
return t
|
||||
}
|
||||
|
||||
// init initializes SampledTimer state that is not preserved across
|
||||
// save/restore. If init has already been called, calling it again is a no-op.
|
||||
//
|
||||
// Preconditions: t.mu must be locked, or the caller must have exclusive access
|
||||
// to t.
|
||||
func (t *SampledTimer) init() {
|
||||
if t.kicker != nil {
|
||||
return
|
||||
}
|
||||
// If t.kicker is nil, the goroutine can't be running, so we can't race
|
||||
// with it.
|
||||
t.kicker = time.NewTimer(0)
|
||||
t.entry, t.events = waiter.NewChannelEntry(timerTickEvents)
|
||||
if err := t.clock.EventRegister(&t.entry); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
go t.runGoroutine() // S/R-SAFE: synchronized by t.mu
|
||||
}
|
||||
|
||||
// Destroy implements Timer.Destroy.
|
||||
func (t *SampledTimer) Destroy() {
|
||||
// Stop the timer, ensuring that the goroutine will not call
|
||||
// t.kicker.Reset, before calling t.kicker.Stop.
|
||||
t.mu.Lock()
|
||||
t.setting.Enabled = false
|
||||
// Set timerDestroyed to prevent t.tick() from mutating timer state.
|
||||
t.pauseState = timerDestroyed
|
||||
t.mu.Unlock()
|
||||
t.kicker.Stop()
|
||||
// Unregister t.entry, ensuring that the Clock will not send to t.events,
|
||||
// before closing t.events to instruct the goroutine to exit.
|
||||
t.clock.EventUnregister(&t.entry)
|
||||
close(t.events)
|
||||
}
|
||||
|
||||
// Pause implements Timer.Pause.
|
||||
func (t *SampledTimer) Pause() {
|
||||
t.mu.Lock()
|
||||
defer t.mu.Unlock()
|
||||
if t.pauseState != timerUnpaused {
|
||||
return
|
||||
}
|
||||
t.pauseState = timerPaused
|
||||
// t.kicker may be nil if we were restored but never resumed.
|
||||
if t.kicker != nil {
|
||||
t.kicker.Stop()
|
||||
}
|
||||
}
|
||||
|
||||
// Resume implements Timer.Resume.
|
||||
func (t *SampledTimer) Resume() {
|
||||
t.mu.Lock()
|
||||
defer t.mu.Unlock()
|
||||
if t.pauseState != timerPaused {
|
||||
return
|
||||
}
|
||||
t.pauseState = timerUnpaused
|
||||
|
||||
// Lazily initialize the SampledTimer. We can't call SampledTimer.init
|
||||
// until SampledTimer.Resume because save/restore will restore Timers
|
||||
// before kernel.Timekeeper.SetClocks() has been called, so if t.clock is
|
||||
// backed by a kernel.Timekeeper then the goroutine will panic if it calls
|
||||
// t.clock.Now().
|
||||
t.init()
|
||||
|
||||
// Kick the goroutine in case it was already initialized, but the goroutine
|
||||
// was sleeping.
|
||||
t.kicker.Reset(0)
|
||||
}
|
||||
|
||||
// Clock implements Timer.Clock.
|
||||
func (t *SampledTimer) Clock() Clock {
|
||||
return SeqAtomicLoadSampledClock(&t.clockSeq, &t.clock)
|
||||
}
|
||||
|
||||
// Get implements Timer.Get.
|
||||
func (t *SampledTimer) Get() (Time, Setting) {
|
||||
// Optimistically read t.Clock().Now() before locking t.mu, as t.clock is
|
||||
// unlikely to change.
|
||||
unlockedClock := t.Clock()
|
||||
now := unlockedClock.Now()
|
||||
t.mu.Lock()
|
||||
defer t.mu.Unlock()
|
||||
if t.pauseState != timerUnpaused {
|
||||
panic(fmt.Sprintf("SampledTimer(%p).Get called in pause state %v", t, t.pauseState))
|
||||
}
|
||||
if t.clock != unlockedClock {
|
||||
now = t.clock.Now()
|
||||
}
|
||||
s, exp := t.setting.At(now)
|
||||
t.setting = s
|
||||
if exp > 0 {
|
||||
t.listener.NotifyTimer(exp)
|
||||
}
|
||||
t.resetKickerLocked(now)
|
||||
return now, s
|
||||
}
|
||||
|
||||
// Set implements Timer.Set.
|
||||
func (t *SampledTimer) Set(s Setting, f func()) (Time, Setting) {
|
||||
// Optimistically read t.Clock().Now() before locking t.mu, as t.clock is
|
||||
// unlikely to change.
|
||||
unlockedClock := t.Clock()
|
||||
now := unlockedClock.Now()
|
||||
t.mu.Lock()
|
||||
defer t.mu.Unlock()
|
||||
if t.pauseState != timerUnpaused {
|
||||
panic(fmt.Sprintf("SampledTimer(%p).Set called in pause state %v", t, t.pauseState))
|
||||
}
|
||||
if t.clock != unlockedClock {
|
||||
now = t.clock.Now()
|
||||
}
|
||||
oldS, oldExp := t.setting.At(now)
|
||||
if oldExp > 0 {
|
||||
t.listener.NotifyTimer(oldExp)
|
||||
}
|
||||
if f != nil {
|
||||
f()
|
||||
}
|
||||
newS, newExp := s.At(now)
|
||||
t.setting = newS
|
||||
if newExp > 0 {
|
||||
t.listener.NotifyTimer(newExp)
|
||||
}
|
||||
t.resetKickerLocked(now)
|
||||
return now, oldS
|
||||
}
|
||||
|
||||
// SetClock atomically changes a SampledTimer's Clock and Setting.
|
||||
func (t *SampledTimer) SetClock(c SampledClock, s Setting) {
|
||||
var now Time
|
||||
if s.Enabled {
|
||||
now = c.Now()
|
||||
}
|
||||
t.mu.Lock()
|
||||
defer t.mu.Unlock()
|
||||
t.setting = s
|
||||
if oldC := t.clock; oldC != c {
|
||||
oldC.EventUnregister(&t.entry)
|
||||
c.EventRegister(&t.entry)
|
||||
t.clockSeq.BeginWrite()
|
||||
t.clock = c
|
||||
t.clockSeq.EndWrite()
|
||||
}
|
||||
t.resetKickerLocked(now)
|
||||
}
|
||||
|
||||
func (t *SampledTimer) runGoroutine() {
|
||||
for {
|
||||
select {
|
||||
case <-t.kicker.C:
|
||||
case _, ok := <-t.events:
|
||||
if !ok {
|
||||
// Channel closed by Destroy.
|
||||
return
|
||||
}
|
||||
}
|
||||
t.tick()
|
||||
}
|
||||
}
|
||||
|
||||
// tick requests that the SampledTimer immediately check for expirations and
|
||||
// re-evaluate when it should next check for expirations.
|
||||
func (t *SampledTimer) tick() {
|
||||
// Optimistically read t.Clock().Now() before locking t.mu, as t.clock is
|
||||
// unlikely to change.
|
||||
unlockedClock := t.Clock()
|
||||
now := unlockedClock.Now()
|
||||
t.mu.Lock()
|
||||
defer t.mu.Unlock()
|
||||
if t.pauseState != timerUnpaused {
|
||||
return
|
||||
}
|
||||
if t.clock != unlockedClock {
|
||||
now = t.clock.Now()
|
||||
}
|
||||
s, exp := t.setting.At(now)
|
||||
t.setting = s
|
||||
if exp > 0 {
|
||||
t.listener.NotifyTimer(exp)
|
||||
}
|
||||
t.resetKickerLocked(now)
|
||||
}
|
||||
|
||||
// Preconditions: t.mu must be locked.
|
||||
func (t *SampledTimer) resetKickerLocked(now Time) {
|
||||
if t.setting.Enabled {
|
||||
// Clock.WallTimeUntil may return a negative value. This is fine;
|
||||
// time.when treats negative Durations as 0.
|
||||
t.kicker.Reset(t.clock.WallTimeUntil(t.setting.Next, now))
|
||||
}
|
||||
// We don't call t.kicker.Stop if !t.setting.Enabled because in most cases
|
||||
// resetKickerLocked will be called from the SampledTimer goroutine, in
|
||||
// which case t.kicker has already fired and t.kicker.Stop will be an
|
||||
// expensive no-op (time.Timer.Stop => time.stopTimer => runtime.stopTimer
|
||||
// => runtime.deltimer).
|
||||
}
|
||||
|
||||
// A SampledClock is a Clock that can be a time source for a SampledTimer.
|
||||
type SampledClock interface {
|
||||
Clock
|
||||
|
||||
// WallTimeUntil returns the estimated wall time until Now will return a
|
||||
// value greater than or equal to t, given that a recent call to Now
|
||||
// returned now. If t has already passed, WallTimeUntil may return 0 or a
|
||||
// negative value.
|
||||
//
|
||||
// WallTimeUntil must be abstract to support SampledClocks that do not
|
||||
// represent wall time (e.g. thread group execution timers). SampledClocks
|
||||
// that represent wall times may embed the WallRateClock type to obtain an
|
||||
// appropriate trivial implementation of WallTimeUntil.
|
||||
//
|
||||
// WallTimeUntil is used to determine when associated SampledTimers should
|
||||
// next check for expirations. Returning too small a value may result in
|
||||
// spurious SampledTimer goroutine wakeups, while returning too large a
|
||||
// value may result in late expirations. Implementations should usually err
|
||||
// on the side of underestimating.
|
||||
WallTimeUntil(t, now Time) time.Duration
|
||||
|
||||
// Waitable methods may be used to subscribe to SampledClock events.
|
||||
// Waiters will not be preserved by Save and must be re-established during
|
||||
// restore.
|
||||
//
|
||||
// Since SampledClock events are transient, implementations of
|
||||
// waiter.Waitable.Readiness should return 0.
|
||||
waiter.Waitable
|
||||
}
|
||||
|
||||
// Events that may be generated by a SampledClock.
|
||||
const (
|
||||
// ClockEventSet occurs when a SampledClock undergoes a discontinuous
|
||||
// change.
|
||||
ClockEventSet waiter.EventMask = 1 << iota
|
||||
|
||||
// ClockEventRateIncrease occurs when the rate at which a SampledClock
|
||||
// advances increases significantly, such that values returned by previous
|
||||
// calls to Clock.WallTimeUntil may be too large.
|
||||
ClockEventRateIncrease
|
||||
)
|
||||
|
||||
// timerTickEvents are SampledClock events that require the Timer goroutine to
|
||||
// Tick prematurely.
|
||||
const timerTickEvents = ClockEventSet | ClockEventRateIncrease
|
||||
|
||||
// WallRateClock implements SampledClock.WallTimeUntil for Clocks that elapse
|
||||
// at the same rate as wall time.
|
||||
type WallRateClock struct{}
|
||||
|
||||
// WallTimeUntil implements SampledClock.WallTimeUntil.
|
||||
func (*WallRateClock) WallTimeUntil(t, now Time) time.Duration {
|
||||
return t.Sub(now)
|
||||
}
|
||||
|
||||
// NoClockEvents implements waiter.Waitable for SampledClocks that do not
|
||||
// generate events.
|
||||
type NoClockEvents struct{}
|
||||
|
||||
// Readiness implements waiter.Waitable.Readiness.
|
||||
func (*NoClockEvents) Readiness(mask waiter.EventMask) waiter.EventMask {
|
||||
return 0
|
||||
}
|
||||
|
||||
// EventRegister implements waiter.Waitable.EventRegister.
|
||||
func (*NoClockEvents) EventRegister(e *waiter.Entry) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// EventUnregister implements waiter.Waitable.EventUnregister.
|
||||
func (*NoClockEvents) EventUnregister(e *waiter.Entry) {
|
||||
}
|
||||
|
||||
// ClockEventsQueue implements waiter.Waitable by wrapping waiter.Queue and
|
||||
// defining waiter.Waitable.Readiness as required by SampledClock.
|
||||
type ClockEventsQueue struct {
|
||||
waiter.Queue
|
||||
}
|
||||
|
||||
// EventRegister implements waiter.Waitable.
|
||||
func (c *ClockEventsQueue) EventRegister(e *waiter.Entry) error {
|
||||
c.Queue.EventRegister(e)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Readiness implements waiter.Waitable.Readiness.
|
||||
func (*ClockEventsQueue) Readiness(mask waiter.EventMask) waiter.EventMask {
|
||||
return 0
|
||||
}
|
||||
@@ -81,7 +81,7 @@ type VariableTimer struct {
|
||||
// called since Timer cannot be restarted once it has been Destroyed by Stop.
|
||||
//
|
||||
// This field is nil iff Stop has been called.
|
||||
t *Timer
|
||||
t Timer
|
||||
}
|
||||
|
||||
// Stop implements tcpip.Timer.Stop.
|
||||
@@ -92,7 +92,7 @@ func (r *VariableTimer) Stop() bool {
|
||||
if r.t == nil {
|
||||
return false
|
||||
}
|
||||
_, lastSetting := r.t.Swap(Setting{})
|
||||
_, lastSetting := r.t.Set(Setting{}, nil)
|
||||
r.t.Destroy()
|
||||
r.t = nil
|
||||
return lastSetting.Enabled
|
||||
@@ -104,14 +104,14 @@ func (r *VariableTimer) Reset(d time.Duration) {
|
||||
defer r.mu.Unlock()
|
||||
|
||||
if r.t == nil {
|
||||
r.t = NewTimer(r.clock, &r.notifier)
|
||||
r.t = r.clock.NewTimer(&r.notifier)
|
||||
}
|
||||
|
||||
r.t.Swap(Setting{
|
||||
r.t.Set(Setting{
|
||||
Enabled: true,
|
||||
Period: 0,
|
||||
Next: r.clock.Now().Add(d),
|
||||
})
|
||||
}, nil)
|
||||
}
|
||||
|
||||
// functionNotifier is a TimerListener that runs a function.
|
||||
|
||||
Reference in New Issue
Block a user