github.com/pingcap/tiflow@v0.0.0-20240520035814-5bf52d54e205/pkg/workerpool/pool_test.go (about) 1 // Copyright 2020 PingCAP, Inc. 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 // See the License for the specific language governing permissions and 12 // limitations under the License. 13 14 package workerpool 15 16 import ( 17 "context" 18 "sync" 19 "sync/atomic" 20 "testing" 21 "time" 22 23 "github.com/pingcap/errors" 24 "github.com/pingcap/failpoint" 25 "github.com/pingcap/log" 26 cerror "github.com/pingcap/tiflow/pkg/errors" 27 "github.com/stretchr/testify/require" 28 "go.uber.org/zap" 29 "golang.org/x/sync/errgroup" 30 "golang.org/x/time/rate" 31 ) 32 33 func TestTaskError(t *testing.T) { 34 ctx, cancel := context.WithTimeout(context.Background(), time.Second*10) 35 36 pool := newDefaultPoolImpl(&defaultHasher{}, 4) 37 errg, ctx := errgroup.WithContext(ctx) 38 errg.Go(func() error { 39 return pool.Run(ctx) 40 }) 41 42 handle := pool.RegisterEvent(func(ctx context.Context, event interface{}) error { 43 if event.(int) == 3 { 44 return errors.New("test error") 45 } 46 return nil 47 }).OnExit(func(err error) { 48 require.Regexp(t, "test error", err) 49 }) 50 51 var wg sync.WaitGroup 52 wg.Add(1) 53 go func() { 54 defer wg.Done() 55 for i := 0; i < 10; i++ { 56 err := handle.AddEvent(ctx, i) 57 if err != nil { 58 require.Regexp(t, ".*ErrWorkerPoolHandleCancelled.*", err) 59 } 60 } 61 }() 62 63 select { 64 case <-ctx.Done(): 65 require.FailNow(t, "fail") 66 case err := <-handle.ErrCh(): 67 require.Regexp(t, "test error", err) 68 } 69 // Only cancel the context after all events have been sent, 70 // otherwise the event delivery may fail due to context cancellation. 71 wg.Wait() 72 cancel() 73 74 err := errg.Wait() 75 require.Regexp(t, "context canceled", err) 76 } 77 78 func TestTimerError(t *testing.T) { 79 ctx, cancel := context.WithTimeout(context.Background(), time.Second*10) 80 defer cancel() 81 82 pool := newDefaultPoolImpl(&defaultHasher{}, 4) 83 errg, ctx := errgroup.WithContext(ctx) 84 errg.Go(func() error { 85 return pool.Run(ctx) 86 }) 87 88 counter := 0 89 handle := pool.RegisterEvent(func(ctx context.Context, event interface{}) error { 90 return nil 91 }).SetTimer(ctx, time.Millisecond*200, func(ctx context.Context) error { 92 if counter == 3 { 93 return errors.New("timer error") 94 } 95 counter++ 96 return nil 97 }) 98 99 select { 100 case <-ctx.Done(): 101 require.FailNow(t, "fail") 102 case err := <-handle.ErrCh(): 103 require.Regexp(t, "timer error", err) 104 } 105 cancel() 106 107 err := errg.Wait() 108 require.Regexp(t, "context canceled", err) 109 } 110 111 func TestMultiError(t *testing.T) { 112 ctx, cancel := context.WithTimeout(context.Background(), time.Second*10) 113 114 pool := newDefaultPoolImpl(&defaultHasher{}, 4) 115 errg, ctx := errgroup.WithContext(ctx) 116 errg.Go(func() error { 117 return pool.Run(ctx) 118 }) 119 120 handle := pool.RegisterEvent(func(ctx context.Context, event interface{}) error { 121 if event.(int) >= 3 { 122 return errors.New("test error") 123 } 124 return nil 125 }) 126 127 var wg sync.WaitGroup 128 wg.Add(1) 129 go func() { 130 defer wg.Done() 131 for i := 0; i < 10; i++ { 132 err := handle.AddEvent(ctx, i) 133 if err != nil { 134 require.Regexp(t, ".*ErrWorkerPoolHandleCancelled.*", err) 135 } 136 } 137 }() 138 139 select { 140 case <-ctx.Done(): 141 require.FailNow(t, "fail") 142 case err := <-handle.ErrCh(): 143 require.Regexp(t, "test error", err) 144 } 145 // Only cancel the context after all events have been sent, 146 // otherwise the event delivery may fail due to context cancellation. 147 wg.Wait() 148 cancel() 149 150 err := errg.Wait() 151 require.Regexp(t, "context canceled", err) 152 } 153 154 func TestCancelHandle(t *testing.T) { 155 ctx, cancel := context.WithTimeout(context.Background(), time.Second*10) 156 defer cancel() 157 158 pool := newDefaultPoolImpl(&defaultHasher{}, 4) 159 errg, ctx := errgroup.WithContext(ctx) 160 errg.Go(func() error { 161 return pool.Run(ctx) 162 }) 163 164 var num int32 165 handle := pool.RegisterEvent(func(ctx context.Context, event interface{}) error { 166 atomic.StoreInt32(&num, int32(event.(int))) 167 return nil 168 }) 169 170 errg.Go(func() error { 171 i := 0 172 for { 173 select { 174 case <-ctx.Done(): 175 return ctx.Err() 176 default: 177 } 178 err := handle.AddEvent(ctx, i) 179 if err != nil { 180 require.Regexp(t, ".*ErrWorkerPoolHandleCancelled.*", err) 181 require.GreaterOrEqual(t, i, 5000) 182 return nil 183 } 184 i++ 185 } 186 }) 187 188 for { 189 select { 190 case <-ctx.Done(): 191 require.FailNow(t, "fail") 192 default: 193 } 194 if atomic.LoadInt32(&num) > 5000 { 195 break 196 } 197 } 198 199 err := failpoint.Enable("github.com/pingcap/tiflow/pkg/workerpool/addEventDelayPoint", "1*sleep(500)") 200 require.Nil(t, err) 201 defer func() { 202 _ = failpoint.Disable("github.com/pingcap/tiflow/pkg/workerpool/addEventDelayPoint") 203 }() 204 205 handle.Unregister() 206 handle.Unregister() // Unregistering many times does not matter 207 handle.Unregister() 208 209 lastNum := atomic.LoadInt32(&num) 210 for i := 0; i <= 1000; i++ { 211 require.Equal(t, atomic.LoadInt32(&num), lastNum) 212 } 213 214 time.Sleep(1 * time.Second) 215 cancel() 216 217 err = errg.Wait() 218 require.Regexp(t, "context canceled", err) 219 } 220 221 func TestCancelTimer(t *testing.T) { 222 ctx, cancel := context.WithTimeout(context.Background(), time.Second*10) 223 defer cancel() 224 225 pool := newDefaultPoolImpl(&defaultHasher{}, 4) 226 errg, ctx := errgroup.WithContext(ctx) 227 errg.Go(func() error { 228 return pool.Run(ctx) 229 }) 230 231 err := failpoint.Enable("github.com/pingcap/tiflow/pkg/workerpool/unregisterDelayPoint", "sleep(5000)") 232 require.Nil(t, err) 233 defer func() { 234 _ = failpoint.Disable("github.com/pingcap/tiflow/pkg/workerpool/unregisterDelayPoint") 235 }() 236 237 handle := pool.RegisterEvent(func(ctx context.Context, event interface{}) error { 238 return nil 239 }).SetTimer(ctx, 200*time.Millisecond, func(ctx context.Context) error { 240 return nil 241 }) 242 243 errg.Go(func() error { 244 i := 0 245 for { 246 err := handle.AddEvent(ctx, i) 247 if err != nil { 248 require.Regexp(t, ".*ErrWorkerPoolHandleCancelled.*", err) 249 return nil 250 } 251 i++ 252 } 253 }) 254 255 handle.Unregister() 256 257 cancel() 258 err = errg.Wait() 259 require.Regexp(t, "context canceled", err) 260 } 261 262 func TestErrorAndCancelRace(t *testing.T) { 263 ctx, cancel := context.WithTimeout(context.Background(), time.Second*10) 264 defer cancel() 265 266 pool := newDefaultPoolImpl(&defaultHasher{}, 4) 267 errg, ctx := errgroup.WithContext(ctx) 268 errg.Go(func() error { 269 return pool.Run(ctx) 270 }) 271 272 var racedVar int 273 handle := pool.RegisterEvent(func(ctx context.Context, event interface{}) error { 274 return errors.New("fake") 275 }).OnExit(func(err error) { 276 time.Sleep(100 * time.Millisecond) 277 racedVar++ 278 }) 279 280 err := handle.AddEvent(ctx, 0) 281 require.NoError(t, err) 282 283 time.Sleep(50 * time.Millisecond) 284 handle.Unregister() 285 racedVar++ 286 287 cancel() 288 err = errg.Wait() 289 require.Regexp(t, "context canceled", err) 290 } 291 292 func TestTimer(t *testing.T) { 293 ctx, cancel := context.WithTimeout(context.Background(), time.Second*10) 294 defer cancel() 295 296 pool := newDefaultPoolImpl(&defaultHasher{}, 4) 297 errg, ctx := errgroup.WithContext(ctx) 298 errg.Go(func() error { 299 return pool.Run(ctx) 300 }) 301 302 time.Sleep(200 * time.Millisecond) 303 304 handle := pool.RegisterEvent(func(ctx context.Context, event interface{}) error { 305 if event.(int) == 3 { 306 return errors.New("test error") 307 } 308 return nil 309 }) 310 311 var lastTime time.Time 312 count := 0 313 handle.SetTimer(ctx, time.Second*1, func(ctx context.Context) error { 314 if !lastTime.IsZero() { 315 require.GreaterOrEqual(t, time.Since(lastTime), 900*time.Millisecond) 316 require.LessOrEqual(t, time.Since(lastTime), 1200*time.Millisecond) 317 } 318 if count == 3 { 319 cancel() 320 return nil 321 } 322 count++ 323 324 lastTime = time.Now() 325 return nil 326 }) 327 328 err := errg.Wait() 329 require.Regexp(t, "context canceled", err) 330 } 331 332 func TestBasics(t *testing.T) { 333 ctx, cancel := context.WithTimeout(context.Background(), time.Second*20) 334 defer cancel() 335 336 pool := newDefaultPoolImpl(&defaultHasher{}, 4) 337 errg, ctx := errgroup.WithContext(ctx) 338 339 errg.Go(func() error { 340 return pool.Run(ctx) 341 }) 342 343 var wg sync.WaitGroup 344 345 wg.Add(16) 346 for i := 0; i < 16; i++ { 347 finalI := i 348 resultCh := make(chan int, 128) 349 handler := pool.RegisterEvent(func(ctx context.Context, event interface{}) error { 350 select { 351 case <-ctx.Done(): 352 return ctx.Err() 353 case resultCh <- event.(int): 354 } 355 log.Debug("result added", zap.Int("id", finalI), zap.Int("result", event.(int))) 356 return nil 357 }) 358 359 errg.Go(func() error { 360 for j := 0; j < 256; j++ { 361 err := handler.AddEvent(ctx, j) 362 if err != nil { 363 return errors.Trace(err) 364 } 365 } 366 return nil 367 }) 368 369 errg.Go(func() error { 370 defer wg.Done() 371 nextExpected := 0 372 for n := range resultCh { 373 select { 374 case <-ctx.Done(): 375 return ctx.Err() 376 default: 377 } 378 log.Debug("result received", zap.Int("id", finalI), zap.Int("result", n)) 379 require.Equal(t, n, nextExpected) 380 nextExpected++ 381 if nextExpected == 256 { 382 break 383 } 384 } 385 return nil 386 }) 387 } 388 389 wg.Wait() 390 cancel() 391 392 err := errg.Wait() 393 require.Regexp(t, "context canceled", err) 394 } 395 396 // TestCancelByAddEventContext makes sure that the event handle can be cancelled by the context used 397 // to call `AddEvent`. 398 func TestCancelByAddEventContext(t *testing.T) { 399 poolCtx, poolCancel := context.WithCancel(context.Background()) 400 defer poolCancel() 401 pool := newDefaultPoolImpl(&defaultHasher{}, 4) 402 go func() { 403 err := pool.Run(poolCtx) 404 require.Regexp(t, ".*context canceled.*", err) 405 }() 406 407 ctx, cancel := context.WithTimeout(context.Background(), time.Second*20) 408 defer cancel() 409 errg, ctx := errgroup.WithContext(ctx) 410 411 for i := 0; i < 8; i++ { 412 handler := pool.RegisterEvent(func(ctx context.Context, event interface{}) error { 413 <-ctx.Done() 414 return ctx.Err() 415 }) 416 417 errg.Go(func() error { 418 for j := 0; j < 64; j++ { 419 err := handler.AddEvent(ctx, j) 420 if err != nil { 421 return nil 422 } 423 } 424 return nil 425 }) 426 427 errg.Go(func() error { 428 select { 429 case <-ctx.Done(): 430 case <-handler.ErrCh(): 431 } 432 return nil 433 }) 434 } 435 436 time.Sleep(5 * time.Second) 437 cancel() 438 439 err := errg.Wait() 440 require.Nil(t, err) 441 } 442 443 func TestGracefulUnregister(t *testing.T) { 444 poolCtx, poolCancel := context.WithCancel(context.Background()) 445 defer poolCancel() 446 pool := newDefaultPoolImpl(&defaultHasher{}, 4) 447 go func() { 448 err := pool.Run(poolCtx) 449 require.Regexp(t, ".*context canceled.*", err) 450 }() 451 452 ctx, cancel := context.WithTimeout(context.Background(), time.Second*20) 453 defer cancel() 454 455 waitCh := make(chan struct{}) 456 457 var lastEventIdx int64 458 handle := pool.RegisterEvent(func(ctx context.Context, event interface{}) error { 459 select { 460 case <-ctx.Done(): 461 return errors.Trace(ctx.Err()) 462 case <-waitCh: 463 } 464 465 idx := event.(int64) 466 old := atomic.SwapInt64(&lastEventIdx, idx) 467 require.Equal(t, old+1, idx) 468 return nil 469 }) 470 471 var wg sync.WaitGroup 472 wg.Add(1) 473 go func() { 474 defer wg.Done() 475 var maxEventIdx int64 476 for i := int64(0); ; i++ { 477 err := handle.AddEvent(ctx, i+1) 478 if cerror.ErrWorkerPoolHandleCancelled.Equal(err) { 479 maxEventIdx = i 480 break 481 } 482 require.NoError(t, err) 483 time.Sleep(time.Millisecond * 10) 484 } 485 486 require.Eventually(t, func() (success bool) { 487 return atomic.LoadInt64(&lastEventIdx) == maxEventIdx 488 }, time.Millisecond*500, time.Millisecond*10) 489 }() 490 491 time.Sleep(time.Millisecond * 200) 492 go func() { 493 close(waitCh) 494 }() 495 err := handle.GracefulUnregister(ctx, time.Second*10) 496 require.NoError(t, err) 497 498 err = handle.AddEvent(ctx, int64(0)) 499 require.Error(t, err) 500 require.True(t, cerror.ErrWorkerPoolHandleCancelled.Equal(err)) 501 require.Equal(t, handleCancelled, handle.(*defaultEventHandle).status) 502 503 wg.Wait() 504 } 505 506 func TestGracefulUnregisterTimeout(t *testing.T) { 507 poolCtx, poolCancel := context.WithCancel(context.Background()) 508 defer poolCancel() 509 pool := newDefaultPoolImpl(&defaultHasher{}, 4) 510 go func() { 511 err := pool.Run(poolCtx) 512 require.Regexp(t, ".*context canceled.*", err) 513 }() 514 515 ctx, cancel := context.WithTimeout(context.Background(), time.Second*20) 516 defer cancel() 517 518 waitCh := make(chan struct{}) 519 520 handle := pool.RegisterEvent(func(ctx context.Context, event interface{}) error { 521 select { 522 case <-waitCh: 523 return nil 524 case <-ctx.Done(): 525 return ctx.Err() 526 } 527 }) 528 529 err := handle.AddEvent(ctx, 0) 530 require.NoError(t, err) 531 532 go func() { 533 time.Sleep(time.Millisecond * 100) 534 close(waitCh) 535 }() 536 err = handle.GracefulUnregister(ctx, time.Millisecond*10) 537 require.Error(t, err) 538 require.Truef(t, cerror.ErrWorkerPoolGracefulUnregisterTimedOut.Equal(err), "%s", err.Error()) 539 } 540 541 func TestSynchronizeLog(t *testing.T) { 542 w := newWorker() 543 w.isRunning = 1 544 // Always report "synchronize is taking too long". 545 w.slowSynchronizeThreshold = time.Duration(0) 546 w.slowSynchronizeLimiter = rate.NewLimiter(rate.Every(100*time.Minute), 1) 547 548 counter := int32(0) 549 logWarn = func(msg string, fields ...zap.Field) { 550 atomic.AddInt32(&counter, 1) 551 } 552 defer func() { logWarn = log.Warn }() 553 554 doneCh := make(chan struct{}) 555 go func() { 556 w.synchronize() 557 close(doneCh) 558 }() 559 560 time.Sleep(300 * time.Millisecond) 561 w.stopNotifier.Notify() 562 time.Sleep(300 * time.Millisecond) 563 w.stopNotifier.Notify() 564 565 // Close worker. 566 atomic.StoreInt32(&w.isRunning, 0) 567 w.stopNotifier.Close() 568 <-doneCh 569 570 require.EqualValues(t, 1, atomic.LoadInt32(&counter)) 571 } 572 573 // Benchmark workerpool with ping-pong workflow. 574 // go test -benchmem -run='^$' -bench '^(BenchmarkWorkerpool)$' github.com/pingcap/tiflow/pkg/workerpool 575 func BenchmarkWorkerpool(b *testing.B) { 576 ctx, cancel := context.WithCancel(context.Background()) 577 defer cancel() 578 579 pool := newDefaultPoolImpl(&defaultHasher{}, 4) 580 go func() { _ = pool.Run(ctx) }() 581 582 ch := make(chan int) 583 handler := pool.RegisterEvent(func(ctx context.Context, event interface{}) error { 584 ch <- event.(int) 585 return nil 586 }) 587 588 b.ResetTimer() 589 for i := 0; i < b.N; i++ { 590 err := handler.AddEvent(ctx, i) 591 if err != nil { 592 b.Fatal(err) 593 } 594 <-ch 595 } 596 }