gvisor.dev/gvisor@v0.0.0-20240520182842-f9d4d51c7e0f/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  	"gvisor.dev/gvisor/pkg/errors/linuxerr"
    23  	ktime "gvisor.dev/gvisor/pkg/sentry/kernel/time"
    24  	"gvisor.dev/gvisor/pkg/sync"
    25  	"gvisor.dev/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  }