gvisor/pkg/sentry/kernel/task_block.go

208 lines
6.2 KiB
Go

// Copyright 2018 Google Inc.
//
// 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 (
"time"
ktime "gvisor.googlesource.com/gvisor/pkg/sentry/kernel/time"
"gvisor.googlesource.com/gvisor/pkg/syserror"
)
// 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 syserror.ErrInterrupted if t is interrupted.
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 err == syserror.ETIMEDOUT {
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
}
// BlockWithDeadline blocks t until an event is received from C, 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
// syserror.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 syserror.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 syserror.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)
}
// block blocks a task on one of many events.
// N.B. defer is too expensive to be used here.
func (t *Task) block(C chan struct{}, timerChan <-chan struct{}) error {
// Fast path if the request is already done.
select {
case <-C:
return nil
default:
}
// Deactive our address space, we don't need it.
interrupt := t.SleepStart()
select {
case <-C:
t.SleepFinish(true)
return nil
case <-interrupt:
t.SleepFinish(false)
// Return the indicated error on interrupt.
return syserror.ErrInterrupted
case <-timerChan:
// We've timed out.
t.SleepFinish(true)
return syserror.ETIMEDOUT
}
}
// SleepStart implements amutex.Sleeper.SleepStart.
func (t *Task) SleepStart() <-chan struct{} {
t.Deactivate()
t.accountTaskGoroutineEnter(TaskGoroutineBlockedInterruptible)
return t.interruptChan
}
// SleepFinish implements amutex.Sleeper.SleepFinish.
func (t *Task) SleepFinish(success bool) {
if !success {
// The interrupted notification is consumed only at the top-level
// (Run). Therefore we attempt to reset the pending notification.
// This will also elide our next entry back into the task, so we
// will process signals, state changes, etc.
t.interruptSelf()
}
t.accountTaskGoroutineLeave(TaskGoroutineBlockedInterruptible)
t.Activate()
}
// UninterruptibleSleepStart implements context.Context.UninterruptibleSleepStart.
func (t *Task) UninterruptibleSleepStart(deactivate bool) {
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 interrupted.
func (t *Task) interrupted() bool {
select {
case <-t.interruptChan:
return true
default:
return false
}
}
// 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{}{}:
t.Debugf("Interrupt queued")
default:
t.Debugf("Dropping duplicate interrupt")
}
// platform.Context.Interrupt() is unnecessary since a task goroutine
// calling interruptSelf() cannot also be blocked in
// platform.Context.Switch().
}