github.com/metacubex/gvisor@v0.0.0-20240320004321-933faba989ec/pkg/sentry/kernel/task_block.go (about) 1 // Copyright 2018 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 kernel 16 17 import ( 18 "runtime" 19 "runtime/trace" 20 "time" 21 22 "github.com/metacubex/gvisor/pkg/errors/linuxerr" 23 ktime "github.com/metacubex/gvisor/pkg/sentry/kernel/time" 24 "github.com/metacubex/gvisor/pkg/sync" 25 "github.com/metacubex/gvisor/pkg/waiter" 26 ) 27 28 // BlockWithTimeout blocks t until an event is received from C, the application 29 // monotonic clock indicates that timeout has elapsed (only if haveTimeout is true), 30 // or t is interrupted. It returns: 31 // 32 // - The remaining timeout, which is guaranteed to be 0 if the timeout expired, 33 // and is unspecified if haveTimeout is false. 34 // 35 // - An error which is nil if an event is received from C, ETIMEDOUT if the timeout 36 // expired, and linuxerr.ErrInterrupted if t is interrupted. 37 // 38 // Preconditions: The caller must be running on the task goroutine. 39 func (t *Task) BlockWithTimeout(C chan struct{}, haveTimeout bool, timeout time.Duration) (time.Duration, error) { 40 if !haveTimeout { 41 return timeout, t.block(C, nil) 42 } 43 44 clock := t.Kernel().MonotonicClock() 45 start := clock.Now() 46 deadline := start.Add(timeout) 47 err := t.BlockWithDeadlineFrom(C, clock, true, deadline) 48 49 // Timeout, explicitly return a remaining duration of 0. 50 if linuxerr.Equals(linuxerr.ETIMEDOUT, err) { 51 return 0, err 52 } 53 54 // Compute the remaining timeout. Note that even if block() above didn't 55 // return due to a timeout, we may have used up any of the remaining time 56 // since then. We cap the remaining timeout to 0 to make it easier to 57 // directly use the returned duration. 58 end := clock.Now() 59 remainingTimeout := timeout - end.Sub(start) 60 if remainingTimeout < 0 { 61 remainingTimeout = 0 62 } 63 64 return remainingTimeout, err 65 } 66 67 // BlockWithTimeoutOn implements context.Context.BlockWithTimeoutOn. 68 func (t *Task) BlockWithTimeoutOn(w waiter.Waitable, mask waiter.EventMask, timeout time.Duration) (time.Duration, bool) { 69 e, ch := waiter.NewChannelEntry(mask) 70 w.EventRegister(&e) 71 defer w.EventUnregister(&e) 72 left, err := t.BlockWithTimeout(ch, true, timeout) 73 return left, err == nil 74 } 75 76 // BlockWithDeadline blocks t until it is woken by an event, the 77 // application monotonic clock indicates a time of deadline (only if 78 // haveDeadline is true), or t is interrupted. It returns nil if an event is 79 // received from C, ETIMEDOUT if the deadline expired, and 80 // linuxerr.ErrInterrupted if t is interrupted. 81 // 82 // Preconditions: The caller must be running on the task goroutine. 83 func (t *Task) BlockWithDeadline(C <-chan struct{}, haveDeadline bool, deadline ktime.Time) error { 84 return t.BlockWithDeadlineFrom(C, t.Kernel().MonotonicClock(), haveDeadline, deadline) 85 } 86 87 // BlockWithDeadlineFrom is similar to BlockWithDeadline, except it uses the 88 // passed clock (instead of application monotonic clock). 89 // 90 // Most clients should use BlockWithDeadline or BlockWithTimeout instead. 91 // 92 // Preconditions: The caller must be running on the task goroutine. 93 func (t *Task) BlockWithDeadlineFrom(C <-chan struct{}, clock ktime.Clock, haveDeadline bool, deadline ktime.Time) error { 94 if !haveDeadline { 95 return t.block(C, nil) 96 } 97 98 // Start the timeout timer. 99 t.blockingTimer.SetClock(clock, ktime.Setting{ 100 Enabled: true, 101 Next: deadline, 102 }) 103 104 err := t.block(C, t.blockingTimerChan) 105 106 // Stop the timeout timer and drain the channel. 107 t.blockingTimer.Swap(ktime.Setting{}) 108 select { 109 case <-t.blockingTimerChan: 110 default: 111 } 112 113 return err 114 } 115 116 // Block implements context.Context.Block 117 func (t *Task) Block(C <-chan struct{}) error { 118 return t.block(C, nil) 119 } 120 121 // BlockOn implements context.Context.BlockOn. 122 func (t *Task) BlockOn(w waiter.Waitable, mask waiter.EventMask) bool { 123 e, ch := waiter.NewChannelEntry(mask) 124 w.EventRegister(&e) 125 defer w.EventUnregister(&e) 126 err := t.Block(ch) 127 return err == nil 128 } 129 130 // block blocks a task on one of many events. 131 // N.B. defer is too expensive to be used here. 132 // 133 // Preconditions: The caller must be running on the task goroutine. 134 func (t *Task) block(C <-chan struct{}, timerChan <-chan struct{}) error { 135 // This function is very hot; skip this check outside of +race builds. 136 if sync.RaceEnabled { 137 t.assertTaskGoroutine() 138 } 139 140 // Fast path if the request is already done. 141 select { 142 case <-C: 143 return nil 144 default: 145 } 146 147 // Deactivate our address space, we don't need it. 148 t.prepareSleep() 149 defer t.completeSleep() 150 151 // If the request is not completed, but the timer has already expired, 152 // then ensure that we run through a scheduler cycle. This is because 153 // we may see applications relying on timer slack to yield the thread. 154 // For example, they may attempt to sleep for some number of nanoseconds, 155 // and expect that this will actually yield the CPU and sleep for at 156 // least microseconds, e.g.: 157 // https://github.com/LMAX-Exchange/disruptor/commit/6ca210f2bcd23f703c479804d583718e16f43c07 158 if len(timerChan) > 0 { 159 runtime.Gosched() 160 } 161 162 region := trace.StartRegion(t.traceContext, blockRegion) 163 select { 164 case <-C: 165 region.End() 166 // Woken by event. 167 return nil 168 169 case <-t.interruptChan: 170 region.End() 171 // Ensure that Task.interrupted() will return true once we return to 172 // the task run loop. 173 t.interruptSelf() 174 // Return the indicated error on interrupt. 175 return linuxerr.ErrInterrupted 176 177 case <-timerChan: 178 region.End() 179 // We've timed out. 180 return linuxerr.ETIMEDOUT 181 } 182 } 183 184 // prepareSleep prepares to sleep. 185 func (t *Task) prepareSleep() { 186 t.assertTaskGoroutine() 187 t.p.PrepareSleep() 188 t.Deactivate() 189 t.accountTaskGoroutineEnter(TaskGoroutineBlockedInterruptible) 190 } 191 192 // completeSleep reactivates the address space. 193 func (t *Task) completeSleep() { 194 t.accountTaskGoroutineLeave(TaskGoroutineBlockedInterruptible) 195 t.Activate() 196 } 197 198 // Interrupted implements context.Context.Interrupted. 199 func (t *Task) Interrupted() bool { 200 if t.interrupted() { 201 return true 202 } 203 // Indicate that t's task goroutine is still responsive (i.e. reset the 204 // watchdog timer). 205 t.accountTaskGoroutineRunning() 206 return false 207 } 208 209 // UninterruptibleSleepStart implements context.Context.UninterruptibleSleepStart. 210 func (t *Task) UninterruptibleSleepStart(deactivate bool) { 211 t.assertTaskGoroutine() 212 if deactivate { 213 t.Deactivate() 214 } 215 t.accountTaskGoroutineEnter(TaskGoroutineBlockedUninterruptible) 216 } 217 218 // UninterruptibleSleepFinish implements context.Context.UninterruptibleSleepFinish. 219 func (t *Task) UninterruptibleSleepFinish(activate bool) { 220 t.accountTaskGoroutineLeave(TaskGoroutineBlockedUninterruptible) 221 if activate { 222 t.Activate() 223 } 224 } 225 226 // interrupted returns true if interrupt or interruptSelf has been called at 227 // least once since the last call to unsetInterrupted. 228 func (t *Task) interrupted() bool { 229 return len(t.interruptChan) != 0 230 } 231 232 // unsetInterrupted causes interrupted to return false until the next call to 233 // interrupt or interruptSelf. 234 func (t *Task) unsetInterrupted() { 235 select { 236 case <-t.interruptChan: 237 default: 238 } 239 } 240 241 // interrupt unblocks the task and interrupts it if it's currently running in 242 // userspace. 243 func (t *Task) interrupt() { 244 t.interruptSelf() 245 t.p.Interrupt() 246 } 247 248 // interruptSelf is like Interrupt, but can only be called by the task 249 // goroutine. 250 func (t *Task) interruptSelf() { 251 select { 252 case t.interruptChan <- struct{}{}: 253 default: 254 } 255 // platform.Context.Interrupt() is unnecessary since a task goroutine 256 // calling interruptSelf() cannot also be blocked in 257 // platform.Context.Switch(). 258 } 259 260 // Interrupt implements context.Blocker.Interrupt. 261 func (t *Task) Interrupt() { 262 t.interrupt() 263 }