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  }