Skip to content

Commit 7920b5b

Browse files
nixprimegvisor-bot
authored andcommitted
kernel: improve tcpip.Timer implementation
- Move ktime.VariableTimer to kernel.timekeeperTcpipTimer, its only use case. This allows timekeeperTcpipTimer to use concrete types kernel.timekeeperClock and ktime.SampledTimer instead of ktime.Clock and ktime.Timer, saving a tiny amount of memory (interface values consist of two pointers) and CPU (for interface method calls). - Fix a bug where timekeeperTcpipTimer expiration can cancel a racing call to timekeeperTcpipTimer.Reset() (see use of new field timekeeperTcpipTimer.resets). - Define Listener.NotifyTimer directly on timekeeperTcpipTimer (dropping ktime.functionNotifier), and move goroutine spawning from the anonymous function in ktime.AfterFunc() into timekeeperTcpipTimer.NotifyTimer(). This slightly simplifies the control flow and saves an allocation for the anonymous function object. - Use monotonicClock rather than realtimeClock. It doesn't make sense for time-of-day clock adjustments to affect netstack timeouts, and this is consistent with tcpip.stdClock => time.AfterFunc => runtime.timer. PiperOrigin-RevId: 695504159
1 parent b9252dc commit 7920b5b

File tree

5 files changed

+122
-154
lines changed

5 files changed

+122
-154
lines changed

pkg/sentry/kernel/BUILD

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,13 @@ declare_mutex(
105105
prefix = "threadGroupTimer",
106106
)
107107

108+
declare_mutex(
109+
name = "timekeeper_tcpip_timer_mutex",
110+
out = "timekeeper_tcpip_timer_mutex.go",
111+
package = "kernel",
112+
prefix = "timekeeperTcpipTimer",
113+
)
114+
108115
declare_mutex(
109116
name = "cgroup_mounts_mutex",
110117
out = "cgroup_mounts_mutex.go",
@@ -290,6 +297,7 @@ go_library(
290297
"threads_impl.go",
291298
"timekeeper.go",
292299
"timekeeper_state.go",
300+
"timekeeper_tcpip_timer_mutex.go",
293301
"tty.go",
294302
"user_counters_mutex.go",
295303
"uts_namespace.go",

pkg/sentry/kernel/kernel.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
//
2121
// Kernel.extMu
2222
// TTY.mu
23+
// timekeeperTcpipTimer.mu
2324
// ThreadGroup.timerMu
2425
// Locks acquired by ktime.Timer methods
2526
// TaskSet.mu

pkg/sentry/kernel/timekeeper.go

Lines changed: 113 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -170,32 +170,6 @@ func (t *Timekeeper) SetClocks(c sentrytime.Clocks, params *VDSOParamPage) {
170170
}
171171
}
172172

173-
var _ tcpip.Clock = (*Timekeeper)(nil)
174-
175-
// Now implements tcpip.Clock.
176-
func (t *Timekeeper) Now() time.Time {
177-
nsec, err := t.GetTime(sentrytime.Realtime)
178-
if err != nil {
179-
panic("timekeeper.GetTime(sentrytime.Realtime): " + err.Error())
180-
}
181-
return time.Unix(0, nsec)
182-
}
183-
184-
// NowMonotonic implements tcpip.Clock.
185-
func (t *Timekeeper) NowMonotonic() tcpip.MonotonicTime {
186-
nsec, err := t.GetTime(sentrytime.Monotonic)
187-
if err != nil {
188-
panic("timekeeper.GetTime(sentrytime.Monotonic): " + err.Error())
189-
}
190-
var mt tcpip.MonotonicTime
191-
return mt.Add(time.Duration(nsec) * time.Nanosecond)
192-
}
193-
194-
// AfterFunc implements tcpip.Clock.
195-
func (t *Timekeeper) AfterFunc(d time.Duration, f func()) tcpip.Timer {
196-
return ktime.AfterFunc(t.realtimeClock, d, f)
197-
}
198-
199173
// startUpdater starts an update goroutine that keeps the clocks updated.
200174
//
201175
// mu must be held.
@@ -354,3 +328,116 @@ func (tc *timekeeperClock) Now() ktime.Time {
354328
func (tc *timekeeperClock) NewTimer(l ktime.Listener) ktime.Timer {
355329
return ktime.NewSampledTimer(tc, l)
356330
}
331+
332+
var _ tcpip.Clock = (*Timekeeper)(nil)
333+
334+
// Now implements tcpip.Clock.
335+
func (t *Timekeeper) Now() time.Time {
336+
nsec, err := t.GetTime(sentrytime.Realtime)
337+
if err != nil {
338+
panic("timekeeper.GetTime(sentrytime.Realtime): " + err.Error())
339+
}
340+
return time.Unix(0, nsec)
341+
}
342+
343+
// NowMonotonic implements tcpip.Clock.
344+
func (t *Timekeeper) NowMonotonic() tcpip.MonotonicTime {
345+
nsec, err := t.GetTime(sentrytime.Monotonic)
346+
if err != nil {
347+
panic("timekeeper.GetTime(sentrytime.Monotonic): " + err.Error())
348+
}
349+
var mt tcpip.MonotonicTime
350+
return mt.Add(time.Duration(nsec) * time.Nanosecond)
351+
}
352+
353+
// AfterFunc implements tcpip.Clock.
354+
func (t *Timekeeper) AfterFunc(d time.Duration, f func()) tcpip.Timer {
355+
timer := &timekeeperTcpipTimer{
356+
clock: t.monotonicClock,
357+
fn: f,
358+
}
359+
timer.Reset(d)
360+
return timer
361+
}
362+
363+
// timekeeperTcpipTimer implements tcpip.Timer by wrapping a ktime.SampledTimer.
364+
// tcpip.Timer does not define a Destroy method, so each timer expiration and
365+
// each call to Timer.Stop() must release all resources by calling
366+
// ktime.SampledTimer.Destroy().
367+
type timekeeperTcpipTimer struct {
368+
// immutable
369+
clock *timekeeperClock
370+
fn func()
371+
372+
// mu protects t.
373+
mu timekeeperTcpipTimerMutex
374+
375+
// t stores the latest running Timer. This is replaced whenever Reset is
376+
// called since Timer cannot be restarted once it has been Destroyed by Stop.
377+
//
378+
// This field is nil iff Stop has been called.
379+
t *ktime.SampledTimer
380+
381+
// resets is the number of times Reset has been called. resets is written
382+
// with both mu and ktime.SampledTimer locks held, so it may be read with
383+
// either or both locks held.
384+
resets int
385+
}
386+
387+
// Stop implements tcpip.Timer.Stop.
388+
func (r *timekeeperTcpipTimer) Stop() bool {
389+
r.mu.Lock()
390+
defer r.mu.Unlock()
391+
392+
if r.t == nil {
393+
return false
394+
}
395+
_, lastSetting := r.t.Set(ktime.Setting{}, nil)
396+
r.t.Destroy()
397+
r.t = nil
398+
return lastSetting.Enabled
399+
}
400+
401+
// stopExpired is equivalent to Stop, but is called when the timer expires.
402+
func (r *timekeeperTcpipTimer) stopExpired(reset int) {
403+
r.mu.Lock()
404+
defer r.mu.Unlock()
405+
406+
if r.t == nil || r.resets != reset {
407+
return
408+
}
409+
r.t.Destroy()
410+
r.t = nil
411+
}
412+
413+
// Reset implements tcpip.Timer.Reset.
414+
func (r *timekeeperTcpipTimer) Reset(d time.Duration) {
415+
r.mu.Lock()
416+
defer r.mu.Unlock()
417+
418+
if r.t == nil {
419+
r.t = ktime.NewSampledTimer(r.clock, r)
420+
}
421+
r.t.Set(ktime.Setting{
422+
Enabled: true,
423+
Next: r.clock.Now().Add(d),
424+
}, r.incResets)
425+
}
426+
427+
func (r *timekeeperTcpipTimer) incResets() {
428+
r.resets++
429+
}
430+
431+
// NotifyTimer implements ktime.Listener.NotifyTimer.
432+
func (r *timekeeperTcpipTimer) NotifyTimer(exp uint64) {
433+
// Implementations of ktime.Listener.NotifyTimer() can't call Timer methods
434+
// due to lock ordering, so we must call r.t.Destroy() from another
435+
// goroutine. We also must call r.stopExpired() rather than r.Stop(), since
436+
// the latter might cancel an unrelated call to r.Reset() that happens
437+
// between now and when this goroutine runs.
438+
thisReset := r.resets
439+
go func() {
440+
r.stopExpired(thisReset)
441+
r.fn()
442+
}()
443+
}

pkg/sentry/ktime/BUILD

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,6 @@ go_library(
6565
"synthetic_timer_list.go",
6666
"synthetic_timer_set.go",
6767
"uint64_range.go",
68-
"util.go",
6968
],
7069
visibility = ["//pkg/sentry:internal"],
7170
deps = [

pkg/sentry/ktime/util.go

Lines changed: 0 additions & 127 deletions
This file was deleted.

0 commit comments

Comments
 (0)