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