gvisor.dev/gvisor@v0.0.0-20240520182842-f9d4d51c7e0f/pkg/syncevent/waiter_test.go (about)

     1  // Copyright 2020 The gVisor Authors.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package syncevent
    16  
    17  import (
    18  	"fmt"
    19  	"testing"
    20  	"time"
    21  
    22  	"gvisor.dev/gvisor/pkg/atomicbitops"
    23  	"gvisor.dev/gvisor/pkg/sleep"
    24  	"gvisor.dev/gvisor/pkg/sync"
    25  )
    26  
    27  func TestWaiterAlreadyPending(t *testing.T) {
    28  	var w Waiter
    29  	w.Init()
    30  	want := Set(1)
    31  	w.Notify(want)
    32  	if got := w.Wait(); got != want {
    33  		t.Errorf("Waiter.Wait: got %#x, wanted %#x", got, want)
    34  	}
    35  }
    36  
    37  func TestWaiterAsyncNotify(t *testing.T) {
    38  	var w Waiter
    39  	w.Init()
    40  	want := Set(1)
    41  	go func() {
    42  		time.Sleep(100 * time.Millisecond)
    43  		w.Notify(want)
    44  	}()
    45  	if got := w.Wait(); got != want {
    46  		t.Errorf("Waiter.Wait: got %#x, wanted %#x", got, want)
    47  	}
    48  }
    49  
    50  func TestWaiterWaitFor(t *testing.T) {
    51  	var w Waiter
    52  	w.Init()
    53  	evWaited := Set(1)
    54  	evOther := Set(2)
    55  	w.Notify(evOther)
    56  	notifiedEvent := atomicbitops.FromUint32(0)
    57  	go func() {
    58  		time.Sleep(100 * time.Millisecond)
    59  		notifiedEvent.Store(1)
    60  		w.Notify(evWaited)
    61  	}()
    62  	if got, want := w.WaitFor(evWaited), evWaited|evOther; got != want {
    63  		t.Errorf("Waiter.WaitFor: got %#x, wanted %#x", got, want)
    64  	}
    65  	if notifiedEvent.Load() == 0 {
    66  		t.Errorf("Waiter.WaitFor returned before goroutine notified waited-for event")
    67  	}
    68  }
    69  
    70  func TestWaiterWaitAndAckAll(t *testing.T) {
    71  	var w Waiter
    72  	w.Init()
    73  	w.Notify(AllEvents)
    74  	if got := w.WaitAndAckAll(); got != AllEvents {
    75  		t.Errorf("Waiter.WaitAndAckAll: got %#x, wanted %#x", got, AllEvents)
    76  	}
    77  	if got := w.Pending(); got != NoEvents {
    78  		t.Errorf("Waiter.WaitAndAckAll did not ack all events: got %#x, wanted 0", got)
    79  	}
    80  }
    81  
    82  // BenchmarkWaiterX, BenchmarkSleeperX, and BenchmarkChannelX benchmark usage
    83  // pattern X (described in terms of Waiter) with Waiter, sleep.Sleeper, and
    84  // buffered chan struct{} respectively. When the maximum number of event
    85  // sources is relevant, we use 3 event sources because this is representative
    86  // of the kernel.Task.block() use case: an interrupt source, a timeout source,
    87  // and the actual event source being waited on.
    88  
    89  // Event set used by most benchmarks.
    90  const evBench Set = 1
    91  
    92  // BenchmarkXxxNotifyRedundant measures how long it takes to notify a Waiter of
    93  // an event that is already pending.
    94  
    95  func BenchmarkWaiterNotifyRedundant(b *testing.B) {
    96  	var w Waiter
    97  	w.Init()
    98  	w.Notify(evBench)
    99  
   100  	b.ResetTimer()
   101  	for i := 0; i < b.N; i++ {
   102  		w.Notify(evBench)
   103  	}
   104  }
   105  
   106  func BenchmarkSleeperNotifyRedundant(b *testing.B) {
   107  	var s sleep.Sleeper
   108  	var w sleep.Waker
   109  	s.AddWaker(&w)
   110  	w.Assert()
   111  
   112  	b.ResetTimer()
   113  	for i := 0; i < b.N; i++ {
   114  		w.Assert()
   115  	}
   116  }
   117  
   118  func BenchmarkChannelNotifyRedundant(b *testing.B) {
   119  	ch := make(chan struct{}, 1)
   120  	ch <- struct{}{}
   121  
   122  	b.ResetTimer()
   123  	for i := 0; i < b.N; i++ {
   124  		select {
   125  		case ch <- struct{}{}:
   126  		default:
   127  		}
   128  	}
   129  }
   130  
   131  // BenchmarkXxxNotifyWaitAck measures how long it takes to notify a Waiter an
   132  // event, return that event using a blocking check, and then unset the event as
   133  // pending.
   134  
   135  func BenchmarkWaiterNotifyWaitAck(b *testing.B) {
   136  	var w Waiter
   137  	w.Init()
   138  
   139  	b.ResetTimer()
   140  	for i := 0; i < b.N; i++ {
   141  		w.Notify(evBench)
   142  		w.Wait()
   143  		w.Ack(evBench)
   144  	}
   145  }
   146  
   147  func BenchmarkSleeperNotifyWaitAck(b *testing.B) {
   148  	var s sleep.Sleeper
   149  	var w sleep.Waker
   150  	s.AddWaker(&w)
   151  
   152  	b.ResetTimer()
   153  	for i := 0; i < b.N; i++ {
   154  		w.Assert()
   155  		s.Fetch(true)
   156  	}
   157  }
   158  
   159  func BenchmarkChannelNotifyWaitAck(b *testing.B) {
   160  	ch := make(chan struct{}, 1)
   161  
   162  	b.ResetTimer()
   163  	for i := 0; i < b.N; i++ {
   164  		// notify
   165  		select {
   166  		case ch <- struct{}{}:
   167  		default:
   168  		}
   169  
   170  		// wait + ack
   171  		<-ch
   172  	}
   173  }
   174  
   175  // BenchmarkSleeperMultiNotifyWaitAck is equivalent to
   176  // BenchmarkSleeperNotifyWaitAck, but also includes allocation of a
   177  // temporary sleep.Waker. This is necessary when multiple goroutines may wait
   178  // for the same event, since each sleep.Waker can wake only a single
   179  // sleep.Sleeper.
   180  //
   181  // The syncevent package does not require a distinct object for each
   182  // waiter-waker relationship, so BenchmarkWaiterNotifyWaitAck and
   183  // BenchmarkWaiterMultiNotifyWaitAck would be identical. The analogous state
   184  // for channels, runtime.sudog, is inescapably runtime-allocated, so
   185  // BenchmarkChannelNotifyWaitAck and BenchmarkChannelMultiNotifyWaitAck would
   186  // also be identical.
   187  
   188  func BenchmarkSleeperMultiNotifyWaitAck(b *testing.B) {
   189  	var s sleep.Sleeper
   190  	// The sleep package doesn't provide sync.Pool allocation of Wakers;
   191  	// we do for a fairer comparison.
   192  	wakerPool := sync.Pool{
   193  		New: func() any {
   194  			return &sleep.Waker{}
   195  		},
   196  	}
   197  
   198  	b.ResetTimer()
   199  	for i := 0; i < b.N; i++ {
   200  		w := wakerPool.Get().(*sleep.Waker)
   201  		s.AddWaker(w)
   202  		w.Assert()
   203  		s.Fetch(true)
   204  		s.Done()
   205  		wakerPool.Put(w)
   206  	}
   207  }
   208  
   209  // BenchmarkXxxTempNotifyWaitAck is equivalent to NotifyWaitAck, but also
   210  // includes allocation of a temporary Waiter. This models the case where a
   211  // goroutine not already associated with a Waiter needs one in order to block.
   212  //
   213  // The analogous state for channels is built into runtime.g, so
   214  // BenchmarkChannelNotifyWaitAck and BenchmarkChannelTempNotifyWaitAck would be
   215  // identical.
   216  
   217  func BenchmarkWaiterTempNotifyWaitAck(b *testing.B) {
   218  	b.ResetTimer()
   219  	for i := 0; i < b.N; i++ {
   220  		w := GetWaiter()
   221  		w.Notify(evBench)
   222  		w.Wait()
   223  		w.Ack(evBench)
   224  		PutWaiter(w)
   225  	}
   226  }
   227  
   228  func BenchmarkSleeperTempNotifyWaitAck(b *testing.B) {
   229  	// The sleep package doesn't provide sync.Pool allocation of Sleepers;
   230  	// we do for a fairer comparison.
   231  	sleeperPool := sync.Pool{
   232  		New: func() any {
   233  			return &sleep.Sleeper{}
   234  		},
   235  	}
   236  	var w sleep.Waker
   237  
   238  	b.ResetTimer()
   239  	for i := 0; i < b.N; i++ {
   240  		s := sleeperPool.Get().(*sleep.Sleeper)
   241  		s.AddWaker(&w)
   242  		w.Assert()
   243  		s.Fetch(true)
   244  		s.Done()
   245  		sleeperPool.Put(s)
   246  	}
   247  }
   248  
   249  // BenchmarkXxxNotifyWaitMultiAck is equivalent to NotifyWaitAck, but allows
   250  // for multiple event sources.
   251  
   252  func BenchmarkWaiterNotifyWaitMultiAck(b *testing.B) {
   253  	var w Waiter
   254  	w.Init()
   255  
   256  	b.ResetTimer()
   257  	for i := 0; i < b.N; i++ {
   258  		w.Notify(evBench)
   259  		if e := w.Wait(); e != evBench {
   260  			b.Fatalf("Wait: got %#x, wanted %#x", e, evBench)
   261  		}
   262  		w.Ack(evBench)
   263  	}
   264  }
   265  
   266  func BenchmarkSleeperNotifyWaitMultiAck(b *testing.B) {
   267  	var s sleep.Sleeper
   268  	var ws [3]sleep.Waker
   269  	for i := range ws {
   270  		s.AddWaker(&ws[i])
   271  	}
   272  
   273  	b.ResetTimer()
   274  	for i := 0; i < b.N; i++ {
   275  		ws[0].Assert()
   276  		if v := s.Fetch(true); v != &ws[0] {
   277  			b.Fatalf("Fetch: got %v, wanted %v", v, &ws[0])
   278  		}
   279  	}
   280  }
   281  
   282  func BenchmarkChannelNotifyWaitMultiAck(b *testing.B) {
   283  	ch0 := make(chan struct{}, 1)
   284  	ch1 := make(chan struct{}, 1)
   285  	ch2 := make(chan struct{}, 1)
   286  
   287  	b.ResetTimer()
   288  	for i := 0; i < b.N; i++ {
   289  		// notify
   290  		select {
   291  		case ch0 <- struct{}{}:
   292  		default:
   293  		}
   294  
   295  		// wait + clear
   296  		select {
   297  		case <-ch0:
   298  			// ok
   299  		case <-ch1:
   300  			b.Fatalf("received from ch1")
   301  		case <-ch2:
   302  			b.Fatalf("received from ch2")
   303  		}
   304  	}
   305  }
   306  
   307  // BenchmarkXxxPingPong exchanges control between two goroutines.
   308  
   309  func BenchmarkWaiterPingPong(b *testing.B) {
   310  	var w1, w2 Waiter
   311  	w1.Init()
   312  	w2.Init()
   313  	var wg sync.WaitGroup
   314  	defer wg.Wait()
   315  
   316  	w1.Notify(evBench)
   317  	b.ResetTimer()
   318  	go func() {
   319  		for i := 0; i < b.N; i++ {
   320  			w1.Wait()
   321  			w1.Ack(evBench)
   322  			w2.Notify(evBench)
   323  		}
   324  	}()
   325  	for i := 0; i < b.N; i++ {
   326  		w2.Wait()
   327  		w2.Ack(evBench)
   328  		w1.Notify(evBench)
   329  	}
   330  }
   331  
   332  func BenchmarkSleeperPingPong(b *testing.B) {
   333  	var (
   334  		s1 sleep.Sleeper
   335  		w1 sleep.Waker
   336  		s2 sleep.Sleeper
   337  		w2 sleep.Waker
   338  	)
   339  	s1.AddWaker(&w1)
   340  	s2.AddWaker(&w2)
   341  	var wg sync.WaitGroup
   342  	defer wg.Wait()
   343  
   344  	w1.Assert()
   345  	wg.Add(1)
   346  	b.ResetTimer()
   347  	go func() {
   348  		defer wg.Done()
   349  		for i := 0; i < b.N; i++ {
   350  			s1.Fetch(true)
   351  			w2.Assert()
   352  		}
   353  	}()
   354  	for i := 0; i < b.N; i++ {
   355  		s2.Fetch(true)
   356  		w1.Assert()
   357  	}
   358  }
   359  
   360  func BenchmarkChannelPingPong(b *testing.B) {
   361  	ch1 := make(chan struct{}, 1)
   362  	ch2 := make(chan struct{}, 1)
   363  	var wg sync.WaitGroup
   364  	defer wg.Wait()
   365  
   366  	ch1 <- struct{}{}
   367  	wg.Add(1)
   368  	b.ResetTimer()
   369  	go func() {
   370  		defer wg.Done()
   371  		for i := 0; i < b.N; i++ {
   372  			<-ch1
   373  			ch2 <- struct{}{}
   374  		}
   375  	}()
   376  	for i := 0; i < b.N; i++ {
   377  		<-ch2
   378  		ch1 <- struct{}{}
   379  	}
   380  }
   381  
   382  // BenchmarkXxxPingPongMulti is equivalent to PingPong, but allows each
   383  // goroutine to receive from multiple event sources (although only one is ever
   384  // signaled).
   385  
   386  func BenchmarkWaiterPingPongMulti(b *testing.B) {
   387  	var w1, w2 Waiter
   388  	w1.Init()
   389  	w2.Init()
   390  	var wg sync.WaitGroup
   391  	defer wg.Wait()
   392  
   393  	w1.Notify(evBench)
   394  	wg.Add(1)
   395  	b.ResetTimer()
   396  	go func() {
   397  		defer wg.Done()
   398  		for i := 0; i < b.N; i++ {
   399  			if e := w1.Wait(); e != evBench {
   400  				// b.Fatalf() can only be called from the main goroutine.
   401  				panic(fmt.Sprintf("Wait: got %#x, wanted %#x", e, evBench))
   402  			}
   403  			w1.Ack(evBench)
   404  			w2.Notify(evBench)
   405  		}
   406  	}()
   407  	for i := 0; i < b.N; i++ {
   408  		if e := w2.Wait(); e != evBench {
   409  			b.Fatalf("Wait: got %#x, wanted %#x", e, evBench)
   410  		}
   411  		w2.Ack(evBench)
   412  		w1.Notify(evBench)
   413  	}
   414  }
   415  
   416  func BenchmarkSleeperPingPongMulti(b *testing.B) {
   417  	var (
   418  		s1           sleep.Sleeper
   419  		w1, w1a, w1b sleep.Waker
   420  		s2           sleep.Sleeper
   421  		w2, w2a, w2b sleep.Waker
   422  	)
   423  	s1.AddWaker(&w1)
   424  	s1.AddWaker(&w1a)
   425  	s1.AddWaker(&w1b)
   426  	s2.AddWaker(&w2)
   427  	s2.AddWaker(&w2a)
   428  	s2.AddWaker(&w2b)
   429  	var wg sync.WaitGroup
   430  	defer wg.Wait()
   431  
   432  	w1.Assert()
   433  	wg.Add(1)
   434  	b.ResetTimer()
   435  	go func() {
   436  		defer wg.Done()
   437  		for i := 0; i < b.N; i++ {
   438  			if w := s1.Fetch(true); w != &w1 {
   439  				// b.Fatalf() can only be called from the main goroutine.
   440  				panic(fmt.Sprintf("Fetch: got %p, wanted %p", w, &w1))
   441  			}
   442  			w2.Assert()
   443  		}
   444  	}()
   445  	for i := 0; i < b.N; i++ {
   446  		if w := s2.Fetch(true); w != &w2 {
   447  			b.Fatalf("Fetch: got %p, wanted %p", w, &w2)
   448  		}
   449  		w1.Assert()
   450  	}
   451  }
   452  
   453  func BenchmarkChannelPingPongMulti(b *testing.B) {
   454  	ch1 := make(chan struct{}, 1)
   455  	ch1a := make(chan struct{}, 1)
   456  	ch1b := make(chan struct{}, 1)
   457  	ch2 := make(chan struct{}, 1)
   458  	ch2a := make(chan struct{}, 1)
   459  	ch2b := make(chan struct{}, 1)
   460  	var wg sync.WaitGroup
   461  	defer wg.Wait()
   462  
   463  	ch1 <- struct{}{}
   464  	wg.Add(1)
   465  	b.ResetTimer()
   466  	go func() {
   467  		defer wg.Done()
   468  		for i := 0; i < b.N; i++ {
   469  			select {
   470  			case <-ch1:
   471  			case <-ch1a:
   472  				panic("received from ch1a")
   473  			case <-ch1b:
   474  				panic("received from ch1a")
   475  			}
   476  			ch2 <- struct{}{}
   477  		}
   478  	}()
   479  	for i := 0; i < b.N; i++ {
   480  		select {
   481  		case <-ch2:
   482  		case <-ch2a:
   483  			panic("received from ch2a")
   484  		case <-ch2b:
   485  			panic("received from ch2a")
   486  		}
   487  		ch1 <- struct{}{}
   488  	}
   489  }