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 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268
|
// Copyright 2018 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 kernel
import (
"runtime"
"runtime/trace"
"time"
"gvisor.dev/gvisor/pkg/errors/linuxerr"
ktime "gvisor.dev/gvisor/pkg/sentry/kernel/time"
"gvisor.dev/gvisor/pkg/sync"
"gvisor.dev/gvisor/pkg/waiter"
)
// BlockWithTimeout blocks t until an event is received from C, the application
// monotonic clock indicates that timeout has elapsed (only if haveTimeout is true),
// or t is interrupted. It returns:
//
// - The remaining timeout, which is guaranteed to be 0 if the timeout expired,
// and is unspecified if haveTimeout is false.
//
// - An error which is nil if an event is received from C, ETIMEDOUT if the timeout
// expired, and linuxerr.ErrInterrupted if t is interrupted.
//
// Preconditions: The caller must be running on the task goroutine.
func (t *Task) BlockWithTimeout(C chan struct{}, haveTimeout bool, timeout time.Duration) (time.Duration, error) {
if !haveTimeout {
return timeout, t.block(C, nil)
}
start := t.Kernel().MonotonicClock().Now()
deadline := start.Add(timeout)
err := t.BlockWithDeadline(C, true, deadline)
// Timeout, explicitly return a remaining duration of 0.
if linuxerr.Equals(linuxerr.ETIMEDOUT, err) {
return 0, err
}
// Compute the remaining timeout. Note that even if block() above didn't
// return due to a timeout, we may have used up any of the remaining time
// since then. We cap the remaining timeout to 0 to make it easier to
// directly use the returned duration.
end := t.Kernel().MonotonicClock().Now()
remainingTimeout := timeout - end.Sub(start)
if remainingTimeout < 0 {
remainingTimeout = 0
}
return remainingTimeout, err
}
// BlockWithTimeoutOn implements context.Context.BlockWithTimeoutOn.
func (t *Task) BlockWithTimeoutOn(w waiter.Waitable, mask waiter.EventMask, timeout time.Duration) (time.Duration, bool) {
e, ch := waiter.NewChannelEntry(mask)
w.EventRegister(&e)
defer w.EventUnregister(&e)
left, err := t.BlockWithTimeout(ch, true, timeout)
return left, err == nil
}
// BlockWithDeadline blocks t until it is woken by an event, the
// application monotonic clock indicates a time of deadline (only if
// haveDeadline is true), or t is interrupted. It returns nil if an event is
// received from C, ETIMEDOUT if the deadline expired, and
// linuxerr.ErrInterrupted if t is interrupted.
//
// Preconditions: The caller must be running on the task goroutine.
func (t *Task) BlockWithDeadline(C <-chan struct{}, haveDeadline bool, deadline ktime.Time) error {
if !haveDeadline {
return t.block(C, nil)
}
// Start the timeout timer.
t.blockingTimer.Swap(ktime.Setting{
Enabled: true,
Next: deadline,
})
err := t.block(C, t.blockingTimerChan)
// Stop the timeout timer and drain the channel.
t.blockingTimer.Swap(ktime.Setting{})
select {
case <-t.blockingTimerChan:
default:
}
return err
}
// BlockWithTimer blocks t until an event is received from C or tchan, or t is
// interrupted. It returns nil if an event is received from C, ETIMEDOUT if an
// event is received from tchan, and linuxerr.ErrInterrupted if t is
// interrupted.
//
// Most clients should use BlockWithDeadline or BlockWithTimeout instead.
//
// Preconditions: The caller must be running on the task goroutine.
func (t *Task) BlockWithTimer(C <-chan struct{}, tchan <-chan struct{}) error {
return t.block(C, tchan)
}
// Block blocks t until an event is received from C or t is interrupted. It
// returns nil if an event is received from C and linuxerr.ErrInterrupted if t
// is interrupted.
//
// Preconditions: The caller must be running on the task goroutine.
func (t *Task) Block(C <-chan struct{}) error {
return t.block(C, nil)
}
// BlockOn implements context.Context.BlockOn.
func (t *Task) BlockOn(w waiter.Waitable, mask waiter.EventMask) bool {
e, ch := waiter.NewChannelEntry(mask)
w.EventRegister(&e)
defer w.EventUnregister(&e)
err := t.Block(ch)
return err == nil
}
// block blocks a task on one of many events.
// N.B. defer is too expensive to be used here.
//
// Preconditions: The caller must be running on the task goroutine.
func (t *Task) block(C <-chan struct{}, timerChan <-chan struct{}) error {
// This function is very hot; skip this check outside of +race builds.
if sync.RaceEnabled {
t.assertTaskGoroutine()
}
// Fast path if the request is already done.
select {
case <-C:
return nil
default:
}
// Deactive our address space, we don't need it.
t.prepareSleep()
defer t.completeSleep()
// If the request is not completed, but the timer has already expired,
// then ensure that we run through a scheduler cycle. This is because
// we may see applications relying on timer slack to yield the thread.
// For example, they may attempt to sleep for some number of nanoseconds,
// and expect that this will actually yield the CPU and sleep for at
// least microseconds, e.g.:
// https://github.com/LMAX-Exchange/disruptor/commit/6ca210f2bcd23f703c479804d583718e16f43c07
if len(timerChan) > 0 {
runtime.Gosched()
}
region := trace.StartRegion(t.traceContext, blockRegion)
select {
case <-C:
region.End()
// Woken by event.
return nil
case <-t.interruptChan:
region.End()
// Ensure that Task.interrupted() will return true once we return to
// the task run loop.
t.interruptSelf()
// Return the indicated error on interrupt.
return linuxerr.ErrInterrupted
case <-timerChan:
region.End()
// We've timed out.
return linuxerr.ETIMEDOUT
}
}
// prepareSleep prepares to sleep.
func (t *Task) prepareSleep() {
t.assertTaskGoroutine()
t.p.PrepareSleep()
t.Deactivate()
t.accountTaskGoroutineEnter(TaskGoroutineBlockedInterruptible)
}
// completeSleep reactivates the address space.
func (t *Task) completeSleep() {
t.accountTaskGoroutineLeave(TaskGoroutineBlockedInterruptible)
t.Activate()
}
// Interrupted implements context.Context.Interrupted.
func (t *Task) Interrupted() bool {
if t.interrupted() {
return true
}
// Indicate that t's task goroutine is still responsive (i.e. reset the
// watchdog timer).
t.accountTaskGoroutineRunning()
return false
}
// UninterruptibleSleepStart implements context.Context.UninterruptibleSleepStart.
func (t *Task) UninterruptibleSleepStart(deactivate bool) {
t.assertTaskGoroutine()
if deactivate {
t.Deactivate()
}
t.accountTaskGoroutineEnter(TaskGoroutineBlockedUninterruptible)
}
// UninterruptibleSleepFinish implements context.Context.UninterruptibleSleepFinish.
func (t *Task) UninterruptibleSleepFinish(activate bool) {
t.accountTaskGoroutineLeave(TaskGoroutineBlockedUninterruptible)
if activate {
t.Activate()
}
}
// interrupted returns true if interrupt or interruptSelf has been called at
// least once since the last call to unsetInterrupted.
func (t *Task) interrupted() bool {
return len(t.interruptChan) != 0
}
// unsetInterrupted causes interrupted to return false until the next call to
// interrupt or interruptSelf.
func (t *Task) unsetInterrupted() {
select {
case <-t.interruptChan:
default:
}
}
// interrupt unblocks the task and interrupts it if it's currently running in
// userspace.
func (t *Task) interrupt() {
t.interruptSelf()
t.p.Interrupt()
}
// interruptSelf is like Interrupt, but can only be called by the task
// goroutine.
func (t *Task) interruptSelf() {
select {
case t.interruptChan <- struct{}{}:
default:
}
// platform.Context.Interrupt() is unnecessary since a task goroutine
// calling interruptSelf() cannot also be blocked in
// platform.Context.Switch().
}
// Interrupt implements context.Blocker.Interrupt.
func (t *Task) Interrupt() {
t.interrupt()
}
|