inet.af/netstack@v0.0.0-20220214151720-7585b01ddccf/tcpip/timer.go (about) 1 // Copyright 2020 The gVisor Authors. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package tcpip 16 17 import ( 18 "time" 19 20 "inet.af/netstack/sync" 21 ) 22 23 // jobInstance is a specific instance of Job. 24 // 25 // Different instances are created each time Job is scheduled so each timer has 26 // its own earlyReturn signal. This is to address a bug when a Job is stopped 27 // and reset in quick succession resulting in a timer instance's earlyReturn 28 // signal being affected or seen by another timer instance. 29 // 30 // Consider the following sceneario where timer instances share a common 31 // earlyReturn signal (T1 creates, stops and resets a Cancellable timer under a 32 // lock L; T2, T3, T4 and T5 are goroutines that handle the first (A), second 33 // (B), third (C), and fourth (D) instance of the timer firing, respectively): 34 // T1: Obtain L 35 // T1: Create a new Job w/ lock L (create instance A) 36 // T2: instance A fires, blocked trying to obtain L. 37 // T1: Attempt to stop instance A (set earlyReturn = true) 38 // T1: Schedule timer (create instance B) 39 // T3: instance B fires, blocked trying to obtain L. 40 // T1: Attempt to stop instance B (set earlyReturn = true) 41 // T1: Schedule timer (create instance C) 42 // T4: instance C fires, blocked trying to obtain L. 43 // T1: Attempt to stop instance C (set earlyReturn = true) 44 // T1: Schedule timer (create instance D) 45 // T5: instance D fires, blocked trying to obtain L. 46 // T1: Release L 47 // 48 // Now that T1 has released L, any of the 4 timer instances can take L and 49 // check earlyReturn. If the timers simply check earlyReturn and then do 50 // nothing further, then instance D will never early return even though it was 51 // not requested to stop. If the timers reset earlyReturn before early 52 // returning, then all but one of the timers will do work when only one was 53 // expected to. If Job resets earlyReturn when resetting, then all the timers 54 // will fire (again, when only one was expected to). 55 // 56 // To address the above concerns the simplest solution was to give each timer 57 // its own earlyReturn signal. 58 type jobInstance struct { 59 timer Timer 60 61 // Used to inform the timer to early return when it gets stopped while the 62 // lock the timer tries to obtain when fired is held (T1 is a goroutine that 63 // tries to cancel the timer and T2 is the goroutine that handles the timer 64 // firing): 65 // T1: Obtain the lock, then call Cancel() 66 // T2: timer fires, and gets blocked on obtaining the lock 67 // T1: Releases lock 68 // T2: Obtains lock does unintended work 69 // 70 // To resolve this, T1 will check to see if the timer already fired, and 71 // inform the timer using earlyReturn to return early so that once T2 obtains 72 // the lock, it will see that it is set to true and do nothing further. 73 earlyReturn *bool 74 } 75 76 // stop stops the job instance j from firing if it hasn't fired already. If it 77 // has fired and is blocked at obtaining the lock, earlyReturn will be set to 78 // true so that it will early return when it obtains the lock. 79 func (j *jobInstance) stop() { 80 if j.timer != nil { 81 j.timer.Stop() 82 *j.earlyReturn = true 83 } 84 } 85 86 // Job represents some work that can be scheduled for execution. The work can 87 // be safely cancelled when it fires at the same time some "related work" is 88 // being done. 89 // 90 // The term "related work" is defined as some work that needs to be done while 91 // holding some lock that the timer must also hold while doing some work. 92 // 93 // Note, it is not safe to copy a Job as its timer instance creates 94 // a closure over the address of the Job. 95 type Job struct { 96 _ sync.NoCopy 97 98 // The clock used to schedule the backing timer 99 clock Clock 100 101 // The active instance of a cancellable timer. 102 instance jobInstance 103 104 // locker is the lock taken by the timer immediately after it fires and must 105 // be held when attempting to stop the timer. 106 // 107 // Must never change after being assigned. 108 locker sync.Locker 109 110 // fn is the function that will be called when a timer fires and has not been 111 // signaled to early return. 112 // 113 // fn MUST NOT attempt to lock locker. 114 // 115 // Must never change after being assigned. 116 fn func() 117 } 118 119 // Cancel prevents the Job from executing if it has not executed already. 120 // 121 // Cancel requires appropriate locking to be in place for any resources managed 122 // by the Job. If the Job is blocked on obtaining the lock when Cancel is 123 // called, it will early return. 124 // 125 // Note, t will be modified. 126 // 127 // j.locker MUST be locked. 128 func (j *Job) Cancel() { 129 j.instance.stop() 130 131 // Nothing to do with the stopped instance anymore. 132 j.instance = jobInstance{} 133 } 134 135 // Schedule schedules the Job for execution after duration d. This can be 136 // called on cancelled or completed Jobs to schedule them again. 137 // 138 // Schedule should be invoked only on unscheduled, cancelled, or completed 139 // Jobs. To be safe, callers should always call Cancel before calling Schedule. 140 // 141 // Note, j will be modified. 142 func (j *Job) Schedule(d time.Duration) { 143 // Create a new instance. 144 earlyReturn := false 145 146 // Capture the locker so that updating the timer does not cause a data race 147 // when a timer fires and tries to obtain the lock (read the timer's locker). 148 locker := j.locker 149 j.instance = jobInstance{ 150 timer: j.clock.AfterFunc(d, func() { 151 locker.Lock() 152 defer locker.Unlock() 153 154 if earlyReturn { 155 // If we reach this point, it means that the timer fired while another 156 // goroutine called Cancel while it had the lock. Simply return here 157 // and do nothing further. 158 earlyReturn = false 159 return 160 } 161 162 j.fn() 163 }), 164 earlyReturn: &earlyReturn, 165 } 166 } 167 168 // NewJob returns a new Job that can be used to schedule f to run in its own 169 // gorountine. l will be locked before calling f then unlocked after f returns. 170 // 171 // var clock tcpip.StdClock 172 // var mu sync.Mutex 173 // message := "foo" 174 // job := tcpip.NewJob(&clock, &mu, func() { 175 // fmt.Println(message) 176 // }) 177 // job.Schedule(time.Second) 178 // 179 // mu.Lock() 180 // message = "bar" 181 // mu.Unlock() 182 // 183 // // Output: bar 184 // 185 // f MUST NOT attempt to lock l. 186 // 187 // l MUST be locked prior to calling the returned job's Cancel(). 188 // 189 // var clock tcpip.StdClock 190 // var mu sync.Mutex 191 // message := "foo" 192 // job := tcpip.NewJob(&clock, &mu, func() { 193 // fmt.Println(message) 194 // }) 195 // job.Schedule(time.Second) 196 // 197 // mu.Lock() 198 // job.Cancel() 199 // mu.Unlock() 200 func NewJob(c Clock, l sync.Locker, f func()) *Job { 201 return &Job{ 202 clock: c, 203 locker: l, 204 fn: f, 205 } 206 }