diff --git a/pkg/sentry/contexttest/contexttest.go b/pkg/sentry/contexttest/contexttest.go index fab40e9cd..21f9be762 100644 --- a/pkg/sentry/contexttest/contexttest.go +++ b/pkg/sentry/contexttest/contexttest.go @@ -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) { diff --git a/pkg/sentry/fsimpl/timerfd/timerfd.go b/pkg/sentry/fsimpl/timerfd/timerfd.go index b727db9c6..92d97b469 100644 --- a/pkg/sentry/fsimpl/timerfd/timerfd.go +++ b/pkg/sentry/fsimpl/timerfd/timerfd.go @@ -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. diff --git a/pkg/sentry/kernel/kernel.go b/pkg/sentry/kernel/kernel.go index bd646c9fc..d96d5aaf6 100644 --- a/pkg/sentry/kernel/kernel.go +++ b/pkg/sentry/kernel/kernel.go @@ -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 } diff --git a/pkg/sentry/kernel/posixtimer.go b/pkg/sentry/kernel/posixtimer.go index 956931157..014ec43a8 100644 --- a/pkg/sentry/kernel/posixtimer.go +++ b/pkg/sentry/kernel/posixtimer.go @@ -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 } diff --git a/pkg/sentry/kernel/task.go b/pkg/sentry/kernel/task.go index 11161ab17..04ecf8588 100644 --- a/pkg/sentry/kernel/task.go +++ b/pkg/sentry/kernel/task.go @@ -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. // diff --git a/pkg/sentry/kernel/task_acct.go b/pkg/sentry/kernel/task_acct.go index 11dc12a4f..01db48358 100644 --- a/pkg/sentry/kernel/task_acct.go +++ b/pkg/sentry/kernel/task_acct.go @@ -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() diff --git a/pkg/sentry/kernel/task_block.go b/pkg/sentry/kernel/task_block.go index e838c3c87..b65dea55c 100644 --- a/pkg/sentry/kernel/task_block.go +++ b/pkg/sentry/kernel/task_block.go @@ -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 diff --git a/pkg/sentry/kernel/task_run.go b/pkg/sentry/kernel/task_run.go index 1d169b74d..0e585ce74 100644 --- a/pkg/sentry/kernel/task_run.go +++ b/pkg/sentry/kernel/task_run.go @@ -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() diff --git a/pkg/sentry/kernel/task_sched.go b/pkg/sentry/kernel/task_sched.go index e041ddfd0..d2c3f3a37 100644 --- a/pkg/sentry/kernel/task_sched.go +++ b/pkg/sentry/kernel/task_sched.go @@ -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 diff --git a/pkg/sentry/kernel/thread_group.go b/pkg/sentry/kernel/thread_group.go index 7f1fd494c..0f6f72d69 100644 --- a/pkg/sentry/kernel/thread_group.go +++ b/pkg/sentry/kernel/thread_group.go @@ -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 diff --git a/pkg/sentry/kernel/timekeeper.go b/pkg/sentry/kernel/timekeeper.go index 12ae7b53b..339efc33a 100644 --- a/pkg/sentry/kernel/timekeeper.go +++ b/pkg/sentry/kernel/timekeeper.go @@ -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) +} diff --git a/pkg/sentry/ktime/BUILD b/pkg/sentry/ktime/BUILD index c52ea7e32..1a8b33913 100644 --- a/pkg/sentry/ktime/BUILD +++ b/pkg/sentry/ktime/BUILD @@ -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"], diff --git a/pkg/sentry/ktime/ktime.go b/pkg/sentry/ktime/ktime.go index 7276281e1..676f2a366 100644 --- a/pkg/sentry/ktime/ktime.go +++ b/pkg/sentry/ktime/ktime.go @@ -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 diff --git a/pkg/sentry/ktime/sampled_timer.go b/pkg/sentry/ktime/sampled_timer.go new file mode 100644 index 000000000..30028d1ea --- /dev/null +++ b/pkg/sentry/ktime/sampled_timer.go @@ -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 +} diff --git a/pkg/sentry/ktime/util.go b/pkg/sentry/ktime/util.go index c7afbee2d..1bddb027d 100644 --- a/pkg/sentry/ktime/util.go +++ b/pkg/sentry/ktime/util.go @@ -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.