gvisor.dev/gvisor@v0.0.0-20240520182842-f9d4d51c7e0f/pkg/syncevent/syncevent_example_test.go (about)

     1  // Copyright 2020 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 syncevent
    16  
    17  import (
    18  	"fmt"
    19  	"time"
    20  
    21  	"gvisor.dev/gvisor/pkg/atomicbitops"
    22  )
    23  
    24  func Example_ioReadinessInterrputible() {
    25  	const (
    26  		evReady = Set(1 << iota)
    27  		evInterrupt
    28  	)
    29  	errNotReady := fmt.Errorf("not ready for I/O")
    30  
    31  	// State of some I/O object.
    32  	var (
    33  		br    Broadcaster
    34  		ready atomicbitops.Uint32
    35  	)
    36  	doIO := func() error {
    37  		if ready.Load() == 0 {
    38  			return errNotReady
    39  		}
    40  		return nil
    41  	}
    42  	go func() {
    43  		// The I/O object eventually becomes ready for I/O.
    44  		time.Sleep(100 * time.Millisecond)
    45  		// When it does, it first ensures that future calls to isReady() return
    46  		// true, then broadcasts the readiness event to Receivers.
    47  		ready.Store(1)
    48  		br.Broadcast(evReady)
    49  	}()
    50  
    51  	// Each user of the I/O object owns a Waiter.
    52  	var w Waiter
    53  	w.Init()
    54  	// The Waiter may be asynchronously interruptible, e.g. for signal
    55  	// handling in the sentry.
    56  	go func() {
    57  		time.Sleep(200 * time.Millisecond)
    58  		w.Receiver().Notify(evInterrupt)
    59  	}()
    60  
    61  	// To use the I/O object:
    62  	//
    63  	// Optionally, if the I/O object is likely to be ready, attempt I/O first.
    64  	err := doIO()
    65  	if err == nil {
    66  		// Success, we're done.
    67  		return /* nil */
    68  	}
    69  	if err != errNotReady {
    70  		// Failure, I/O failed for some reason other than readiness.
    71  		return /* err */
    72  	}
    73  	// Subscribe for readiness events from the I/O object.
    74  	id := br.SubscribeEvents(w.Receiver(), evReady)
    75  	// When we are finished blocking, unsubscribe from readiness events and
    76  	// remove readiness events from the pending event set.
    77  	defer UnsubscribeAndAck(&br, w.Receiver(), evReady, id)
    78  	for {
    79  		// Attempt I/O again. This must be done after the call to SubscribeEvents,
    80  		// since the I/O object might have become ready between the previous call
    81  		// to doIO and the call to SubscribeEvents.
    82  		err = doIO()
    83  		if err == nil {
    84  			return /* nil */
    85  		}
    86  		if err != errNotReady {
    87  			return /* err */
    88  		}
    89  		// Block until either the I/O object indicates it is ready, or we are
    90  		// interrupted.
    91  		events := w.Wait()
    92  		if events&evInterrupt != 0 {
    93  			// In the specific case of sentry signal handling, signal delivery
    94  			// is handled by another system, so we aren't responsible for
    95  			// acknowledging evInterrupt.
    96  			return /* errInterrupted */
    97  		}
    98  		// Note that, in a concurrent context, the I/O object might become
    99  		// ready and then not ready again. To handle this:
   100  		//
   101  		//	- evReady must be acknowledged before calling doIO() again (rather
   102  		//		than after), so that if the I/O object becomes ready *again* after
   103  		//		the call to doIO(), the readiness event is not lost.
   104  		//
   105  		//	- We must loop instead of just calling doIO() once after receiving
   106  		//		evReady.
   107  		w.Ack(evReady)
   108  	}
   109  }