gvisor/pkg/syncevent/waiter_test.go

415 lines
9.1 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 (
"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(&notifiedEvent, 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(&notifiedEvent) == 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")
}
}
}