415 lines
9.1 KiB
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(¬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")
|
||
|
}
|
||
|
}
|
||
|
}
|