summaryrefslogtreecommitdiffhomepage
path: root/pkg/sentry/kernel/time/tcpip.go
blob: c4474c0cf86bc2051a080d0f56ab00b8237f6842 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
// Copyright 2020 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 time

import (
	"sync"
	"time"
)

// TcpipAfterFunc waits for duration to elapse according to clock then runs fn.
// The timer is started immediately and will fire exactly once.
func TcpipAfterFunc(clock Clock, duration time.Duration, fn func()) *TcpipTimer {
	timer := &TcpipTimer{
		clock: clock,
	}
	timer.notifier = functionNotifier{
		fn: func() {
			// tcpip.Timer.Stop() explicitly states that the function is called in a
			// separate goroutine that Stop() does not synchronize with.
			// Timer.Destroy() synchronizes with calls to TimerListener.Notify().
			// This is semantically meaningful because, in the former case, it's
			// legal to call tcpip.Timer.Stop() while holding locks that may also be
			// taken by the function, but this isn't so in the latter case. Most
			// immediately, Timer calls TimerListener.Notify() while holding
			// Timer.mu. A deadlock occurs without spawning a goroutine:
			//   T1: (Timer expires)
			//     => Timer.Tick()           <- Timer.mu.Lock() called
			//     => TimerListener.Notify()
			//     => Timer.Stop()
			//     => Timer.Destroy()        <- Timer.mu.Lock() called, deadlock!
			//
			// Spawning a goroutine avoids the deadlock:
			//   T1: (Timer expires)
			//     => Timer.Tick()           <- Timer.mu.Lock() called
			//     => TimerListener.Notify() <- Launches T2
			//   T2:
			//     => Timer.Stop()
			//     => Timer.Destroy()        <- Timer.mu.Lock() called, blocks
			//   T1:
			//     => (returns)              <- Timer.mu.Unlock() called
			//   T2:
			//     => (continues)            <- No deadlock!
			go func() {
				timer.Stop()
				fn()
			}()
		},
	}
	timer.Reset(duration)
	return timer
}

// TcpipTimer is a resettable timer with variable duration expirations.
// Implements tcpip.Timer, which does not define a Destroy method; instead, all
// resources are released after timer expiration and calls to Timer.Stop.
//
// Must be created by AfterFunc.
type TcpipTimer struct {
	// clock is the time source. clock is immutable.
	clock Clock

	// notifier is called when the Timer expires. notifier is immutable.
	notifier functionNotifier

	// mu protects t.
	mu sync.Mutex

	// t stores the latest running Timer. This is replaced whenever Reset is
	// called since Timer cannot be restarted once it has been Destroyed by Stop.
	//
	// This field is nil iff Stop has been called.
	t *Timer
}

// Stop implements tcpip.Timer.Stop.
func (r *TcpipTimer) Stop() bool {
	r.mu.Lock()
	defer r.mu.Unlock()

	if r.t == nil {
		return false
	}
	_, lastSetting := r.t.Swap(Setting{})
	r.t.Destroy()
	r.t = nil
	return lastSetting.Enabled
}

// Reset implements tcpip.Timer.Reset.
func (r *TcpipTimer) Reset(d time.Duration) {
	r.mu.Lock()
	defer r.mu.Unlock()

	if r.t == nil {
		r.t = NewTimer(r.clock, &r.notifier)
	}

	r.t.Swap(Setting{
		Enabled: true,
		Period:  0,
		Next:    r.clock.Now().Add(d),
	})
}

// functionNotifier is a TimerListener that runs a function.
//
// functionNotifier cannot be saved or loaded.
type functionNotifier struct {
	fn func()
}

// Notify implements ktime.TimerListener.Notify.
func (f *functionNotifier) Notify(uint64, Setting) (Setting, bool) {
	f.fn()
	return Setting{}, false
}

// Destroy implements ktime.TimerListener.Destroy.
func (f *functionNotifier) Destroy() {}