// Copyright 2020 The gVisor Authors. // // 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 syncevent import ( "fmt" "sync/atomic" "time" ) func Example_ioReadinessInterrputible() { const ( evReady = Set(1 << iota) evInterrupt ) errNotReady := fmt.Errorf("not ready for I/O") // State of some I/O object. var ( br Broadcaster ready uint32 ) doIO := func() error { if atomic.LoadUint32(&ready) == 0 { return errNotReady } return nil } go func() { // The I/O object eventually becomes ready for I/O. time.Sleep(100 * time.Millisecond) // When it does, it first ensures that future calls to isReady() return // true, then broadcasts the readiness event to Receivers. atomic.StoreUint32(&ready, 1) br.Broadcast(evReady) }() // Each user of the I/O object owns a Waiter. var w Waiter w.Init() // The Waiter may be asynchronously interruptible, e.g. for signal // handling in the sentry. go func() { time.Sleep(200 * time.Millisecond) w.Receiver().Notify(evInterrupt) }() // To use the I/O object: // // Optionally, if the I/O object is likely to be ready, attempt I/O first. err := doIO() if err == nil { // Success, we're done. return /* nil */ } if err != errNotReady { // Failure, I/O failed for some reason other than readiness. return /* err */ } // Subscribe for readiness events from the I/O object. id := br.SubscribeEvents(w.Receiver(), evReady) // When we are finished blocking, unsubscribe from readiness events and // remove readiness events from the pending event set. defer UnsubscribeAndAck(&br, w.Receiver(), evReady, id) for { // Attempt I/O again. This must be done after the call to SubscribeEvents, // since the I/O object might have become ready between the previous call // to doIO and the call to SubscribeEvents. err = doIO() if err == nil { return /* nil */ } if err != errNotReady { return /* err */ } // Block until either the I/O object indicates it is ready, or we are // interrupted. events := w.Wait() if events&evInterrupt != 0 { // In the specific case of sentry signal handling, signal delivery // is handled by another system, so we aren't responsible for // acknowledging evInterrupt. return /* errInterrupted */ } // Note that, in a concurrent context, the I/O object might become // ready and then not ready again. To handle this: // // - evReady must be acknowledged before calling doIO() again (rather // than after), so that if the I/O object becomes ready *again* after // the call to doIO(), the readiness event is not lost. // // - We must loop instead of just calling doIO() once after receiving // evReady. w.Ack(evReady) } }