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
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
|
// 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 tcpip
import (
"time"
"gvisor.dev/gvisor/pkg/sync"
)
// cancellableTimerInstance is a specific instance of CancellableTimer.
//
// Different instances are created each time CancellableTimer is Reset so each
// timer has its own earlyReturn signal. This is to address a bug when a
// CancellableTimer is stopped and reset in quick succession resulting in a
// timer instance's earlyReturn signal being affected or seen by another timer
// instance.
//
// Consider the following sceneario where timer instances share a common
// earlyReturn signal (T1 creates, stops and resets a Cancellable timer under a
// lock L; T2, T3, T4 and T5 are goroutines that handle the first (A), second
// (B), third (C), and fourth (D) instance of the timer firing, respectively):
// T1: Obtain L
// T1: Create a new CancellableTimer w/ lock L (create instance A)
// T2: instance A fires, blocked trying to obtain L.
// T1: Attempt to stop instance A (set earlyReturn = true)
// T1: Reset timer (create instance B)
// T3: instance B fires, blocked trying to obtain L.
// T1: Attempt to stop instance B (set earlyReturn = true)
// T1: Reset timer (create instance C)
// T4: instance C fires, blocked trying to obtain L.
// T1: Attempt to stop instance C (set earlyReturn = true)
// T1: Reset timer (create instance D)
// T5: instance D fires, blocked trying to obtain L.
// T1: Release L
//
// Now that T1 has released L, any of the 4 timer instances can take L and check
// earlyReturn. If the timers simply check earlyReturn and then do nothing
// further, then instance D will never early return even though it was not
// requested to stop. If the timers reset earlyReturn before early returning,
// then all but one of the timers will do work when only one was expected to.
// If CancellableTimer resets earlyReturn when resetting, then all the timers
// will fire (again, when only one was expected to).
//
// To address the above concerns the simplest solution was to give each timer
// its own earlyReturn signal.
type cancellableTimerInstance struct {
timer *time.Timer
// Used to inform the timer to early return when it gets stopped while the
// lock the timer tries to obtain when fired is held (T1 is a goroutine that
// tries to cancel the timer and T2 is the goroutine that handles the timer
// firing):
// T1: Obtain the lock, then call StopLocked()
// T2: timer fires, and gets blocked on obtaining the lock
// T1: Releases lock
// T2: Obtains lock does unintended work
//
// To resolve this, T1 will check to see if the timer already fired, and
// inform the timer using earlyReturn to return early so that once T2 obtains
// the lock, it will see that it is set to true and do nothing further.
earlyReturn *bool
}
// stop stops the timer instance t from firing if it hasn't fired already. If it
// has fired and is blocked at obtaining the lock, earlyReturn will be set to
// true so that it will early return when it obtains the lock.
func (t *cancellableTimerInstance) stop() {
if t.timer != nil {
t.timer.Stop()
*t.earlyReturn = true
}
}
// CancellableTimer is a timer that does some work and can be safely cancelled
// when it fires at the same time some "related work" is being done.
//
// The term "related work" is defined as some work that needs to be done while
// holding some lock that the timer must also hold while doing some work.
//
// Note, it is not safe to copy a CancellableTimer as its timer instance creates
// a closure over the address of the CancellableTimer.
type CancellableTimer struct {
_ sync.NoCopy
// The active instance of a cancellable timer.
instance cancellableTimerInstance
// locker is the lock taken by the timer immediately after it fires and must
// be held when attempting to stop the timer.
//
// Must never change after being assigned.
locker sync.Locker
// fn is the function that will be called when a timer fires and has not been
// signaled to early return.
//
// fn MUST NOT attempt to lock locker.
//
// Must never change after being assigned.
fn func()
}
// StopLocked prevents the Timer from firing if it has not fired already.
//
// If the timer is blocked on obtaining the t.locker lock when StopLocked is
// called, it will early return instead of calling t.fn.
//
// Note, t will be modified.
//
// t.locker MUST be locked.
func (t *CancellableTimer) StopLocked() {
t.instance.stop()
// Nothing to do with the stopped instance anymore.
t.instance = cancellableTimerInstance{}
}
// Reset changes the timer to expire after duration d.
//
// Note, t will be modified.
//
// Reset should only be called on stopped or expired timers. To be safe, callers
// should always call StopLocked before calling Reset.
func (t *CancellableTimer) Reset(d time.Duration) {
// Create a new instance.
earlyReturn := false
// Capture the locker so that updating the timer does not cause a data race
// when a timer fires and tries to obtain the lock (read the timer's locker).
locker := t.locker
t.instance = cancellableTimerInstance{
timer: time.AfterFunc(d, func() {
locker.Lock()
defer locker.Unlock()
if earlyReturn {
// If we reach this point, it means that the timer fired while another
// goroutine called StopLocked while it had the lock. Simply return
// here and do nothing further.
earlyReturn = false
return
}
t.fn()
}),
earlyReturn: &earlyReturn,
}
}
// NewCancellableTimer returns an unscheduled CancellableTimer with the given
// locker and fn.
//
// fn MUST NOT attempt to lock locker.
//
// Callers must call Reset to schedule the timer to fire.
func NewCancellableTimer(locker sync.Locker, fn func()) *CancellableTimer {
return &CancellableTimer{locker: locker, fn: fn}
}
|