gvisor/pkg/syncevent/syncevent_example_test.go

109 lines
3.2 KiB
Go

// 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)
}
}