// 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 ( "sync/atomic" "testing" "time" "gvisor.dev/gvisor/pkg/sleep" "gvisor.dev/gvisor/pkg/sync" ) func TestWaiterAlreadyPending(t *testing.T) { var w Waiter w.Init() want := Set(1) w.Notify(want) if got := w.Wait(); got != want { t.Errorf("Waiter.Wait: got %#x, wanted %#x", got, want) } } func TestWaiterAsyncNotify(t *testing.T) { var w Waiter w.Init() want := Set(1) go func() { time.Sleep(100 * time.Millisecond) w.Notify(want) }() if got := w.Wait(); got != want { t.Errorf("Waiter.Wait: got %#x, wanted %#x", got, want) } } func TestWaiterWaitFor(t *testing.T) { var w Waiter w.Init() evWaited := Set(1) evOther := Set(2) w.Notify(evOther) notifiedEvent := uint32(0) go func() { time.Sleep(100 * time.Millisecond) atomic.StoreUint32(¬ifiedEvent, 1) w.Notify(evWaited) }() if got, want := w.WaitFor(evWaited), evWaited|evOther; got != want { t.Errorf("Waiter.WaitFor: got %#x, wanted %#x", got, want) } if atomic.LoadUint32(¬ifiedEvent) == 0 { t.Errorf("Waiter.WaitFor returned before goroutine notified waited-for event") } } func TestWaiterWaitAndAckAll(t *testing.T) { var w Waiter w.Init() w.Notify(AllEvents) if got := w.WaitAndAckAll(); got != AllEvents { t.Errorf("Waiter.WaitAndAckAll: got %#x, wanted %#x", got, AllEvents) } if got := w.Pending(); got != NoEvents { t.Errorf("Waiter.WaitAndAckAll did not ack all events: got %#x, wanted 0", got) } } // BenchmarkWaiterX, BenchmarkSleeperX, and BenchmarkChannelX benchmark usage // pattern X (described in terms of Waiter) with Waiter, sleep.Sleeper, and // buffered chan struct{} respectively. When the maximum number of event // sources is relevant, we use 3 event sources because this is representative // of the kernel.Task.block() use case: an interrupt source, a timeout source, // and the actual event source being waited on. // Event set used by most benchmarks. const evBench Set = 1 // BenchmarkXxxNotifyRedundant measures how long it takes to notify a Waiter of // an event that is already pending. func BenchmarkWaiterNotifyRedundant(b *testing.B) { var w Waiter w.Init() w.Notify(evBench) b.ResetTimer() for i := 0; i < b.N; i++ { w.Notify(evBench) } } func BenchmarkSleeperNotifyRedundant(b *testing.B) { var s sleep.Sleeper var w sleep.Waker s.AddWaker(&w, 0) w.Assert() b.ResetTimer() for i := 0; i < b.N; i++ { w.Assert() } } func BenchmarkChannelNotifyRedundant(b *testing.B) { ch := make(chan struct{}, 1) ch <- struct{}{} b.ResetTimer() for i := 0; i < b.N; i++ { select { case ch <- struct{}{}: default: } } } // BenchmarkXxxNotifyWaitAck measures how long it takes to notify a Waiter an // event, return that event using a blocking check, and then unset the event as // pending. func BenchmarkWaiterNotifyWaitAck(b *testing.B) { var w Waiter w.Init() b.ResetTimer() for i := 0; i < b.N; i++ { w.Notify(evBench) w.Wait() w.Ack(evBench) } } func BenchmarkSleeperNotifyWaitAck(b *testing.B) { var s sleep.Sleeper var w sleep.Waker s.AddWaker(&w, 0) b.ResetTimer() for i := 0; i < b.N; i++ { w.Assert() s.Fetch(true) } } func BenchmarkChannelNotifyWaitAck(b *testing.B) { ch := make(chan struct{}, 1) b.ResetTimer() for i := 0; i < b.N; i++ { // notify select { case ch <- struct{}{}: default: } // wait + ack <-ch } } // BenchmarkSleeperMultiNotifyWaitAck is equivalent to // BenchmarkSleeperNotifyWaitAck, but also includes allocation of a // temporary sleep.Waker. This is necessary when multiple goroutines may wait // for the same event, since each sleep.Waker can wake only a single // sleep.Sleeper. // // The syncevent package does not require a distinct object for each // waiter-waker relationship, so BenchmarkWaiterNotifyWaitAck and // BenchmarkWaiterMultiNotifyWaitAck would be identical. The analogous state // for channels, runtime.sudog, is inescapably runtime-allocated, so // BenchmarkChannelNotifyWaitAck and BenchmarkChannelMultiNotifyWaitAck would // also be identical. func BenchmarkSleeperMultiNotifyWaitAck(b *testing.B) { var s sleep.Sleeper // The sleep package doesn't provide sync.Pool allocation of Wakers; // we do for a fairer comparison. wakerPool := sync.Pool{ New: func() interface{} { return &sleep.Waker{} }, } b.ResetTimer() for i := 0; i < b.N; i++ { w := wakerPool.Get().(*sleep.Waker) s.AddWaker(w, 0) w.Assert() s.Fetch(true) s.Done() wakerPool.Put(w) } } // BenchmarkXxxTempNotifyWaitAck is equivalent to NotifyWaitAck, but also // includes allocation of a temporary Waiter. This models the case where a // goroutine not already associated with a Waiter needs one in order to block. // // The analogous state for channels is built into runtime.g, so // BenchmarkChannelNotifyWaitAck and BenchmarkChannelTempNotifyWaitAck would be // identical. func BenchmarkWaiterTempNotifyWaitAck(b *testing.B) { b.ResetTimer() for i := 0; i < b.N; i++ { w := GetWaiter() w.Notify(evBench) w.Wait() w.Ack(evBench) PutWaiter(w) } } func BenchmarkSleeperTempNotifyWaitAck(b *testing.B) { // The sleep package doesn't provide sync.Pool allocation of Sleepers; // we do for a fairer comparison. sleeperPool := sync.Pool{ New: func() interface{} { return &sleep.Sleeper{} }, } var w sleep.Waker b.ResetTimer() for i := 0; i < b.N; i++ { s := sleeperPool.Get().(*sleep.Sleeper) s.AddWaker(&w, 0) w.Assert() s.Fetch(true) s.Done() sleeperPool.Put(s) } } // BenchmarkXxxNotifyWaitMultiAck is equivalent to NotifyWaitAck, but allows // for multiple event sources. func BenchmarkWaiterNotifyWaitMultiAck(b *testing.B) { var w Waiter w.Init() b.ResetTimer() for i := 0; i < b.N; i++ { w.Notify(evBench) if e := w.Wait(); e != evBench { b.Fatalf("Wait: got %#x, wanted %#x", e, evBench) } w.Ack(evBench) } } func BenchmarkSleeperNotifyWaitMultiAck(b *testing.B) { var s sleep.Sleeper var ws [3]sleep.Waker for i := range ws { s.AddWaker(&ws[i], i) } b.ResetTimer() for i := 0; i < b.N; i++ { ws[0].Assert() if id, _ := s.Fetch(true); id != 0 { b.Fatalf("Fetch: got %d, wanted 0", id) } } } func BenchmarkChannelNotifyWaitMultiAck(b *testing.B) { ch0 := make(chan struct{}, 1) ch1 := make(chan struct{}, 1) ch2 := make(chan struct{}, 1) b.ResetTimer() for i := 0; i < b.N; i++ { // notify select { case ch0 <- struct{}{}: default: } // wait + clear select { case <-ch0: // ok case <-ch1: b.Fatalf("received from ch1") case <-ch2: b.Fatalf("received from ch2") } } } // BenchmarkXxxNotifyAsyncWaitAck measures how long it takes to wait for an // event while another goroutine signals the event. This assumes that a new // goroutine doesn't run immediately (i.e. the creator of a new goroutine is // allowed to go to sleep before the new goroutine has a chance to run). func BenchmarkWaiterNotifyAsyncWaitAck(b *testing.B) { var w Waiter w.Init() b.ResetTimer() for i := 0; i < b.N; i++ { go func() { w.Notify(1) }() w.Wait() w.Ack(evBench) } } func BenchmarkSleeperNotifyAsyncWaitAck(b *testing.B) { var s sleep.Sleeper var w sleep.Waker s.AddWaker(&w, 0) b.ResetTimer() for i := 0; i < b.N; i++ { go func() { w.Assert() }() s.Fetch(true) } } func BenchmarkChannelNotifyAsyncWaitAck(b *testing.B) { ch := make(chan struct{}, 1) b.ResetTimer() for i := 0; i < b.N; i++ { go func() { select { case ch <- struct{}{}: default: } }() <-ch } } // BenchmarkXxxNotifyAsyncWaitMultiAck is equivalent to NotifyAsyncWaitAck, but // allows for multiple event sources. func BenchmarkWaiterNotifyAsyncWaitMultiAck(b *testing.B) { var w Waiter w.Init() b.ResetTimer() for i := 0; i < b.N; i++ { go func() { w.Notify(evBench) }() if e := w.Wait(); e != evBench { b.Fatalf("Wait: got %#x, wanted %#x", e, evBench) } w.Ack(evBench) } } func BenchmarkSleeperNotifyAsyncWaitMultiAck(b *testing.B) { var s sleep.Sleeper var ws [3]sleep.Waker for i := range ws { s.AddWaker(&ws[i], i) } b.ResetTimer() for i := 0; i < b.N; i++ { go func() { ws[0].Assert() }() if id, _ := s.Fetch(true); id != 0 { b.Fatalf("Fetch: got %d, expected 0", id) } } } func BenchmarkChannelNotifyAsyncWaitMultiAck(b *testing.B) { ch0 := make(chan struct{}, 1) ch1 := make(chan struct{}, 1) ch2 := make(chan struct{}, 1) b.ResetTimer() for i := 0; i < b.N; i++ { go func() { select { case ch0 <- struct{}{}: default: } }() select { case <-ch0: // ok case <-ch1: b.Fatalf("received from ch1") case <-ch2: b.Fatalf("received from ch2") } } }