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 }