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