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:
Jamie Liu
2024-11-07 17:19:25 -08:00
committed by gVisor bot
parent 155cdcb458
commit 2e6cfa72f2
15 changed files with 516 additions and 416 deletions
+11 -1
View File
@@ -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) {
+3 -3
View File
@@ -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.
+2 -2
View File
@@ -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
}
+3 -3
View File
@@ -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
}
+6 -4
View File
@@ -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.
//
+1 -1
View File
@@ -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()
+39 -7
View File
@@ -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
+2 -3
View File
@@ -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()
+10
View File
@@ -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
+2 -2
View File
@@ -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
+7 -2
View File
@@ -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)
}
+6 -5
View File
@@ -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
View File
@@ -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
+372
View File
@@ -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
}
+5 -5
View File
@@ -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.