github.com/pingcap/ticdc@v0.0.0-20220526033649-485a10ef2652/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/check" 24 "github.com/pingcap/errors" 25 "github.com/pingcap/failpoint" 26 "github.com/pingcap/log" 27 "github.com/pingcap/ticdc/pkg/util/testleak" 28 "go.uber.org/zap" 29 "golang.org/x/sync/errgroup" 30 ) 31 32 func TestSuite(t *testing.T) { check.TestingT(t) } 33 34 type workerPoolSuite struct{} 35 36 var _ = check.Suite(&workerPoolSuite{}) 37 38 func (s *workerPoolSuite) TestTaskError(c *check.C) { 39 defer testleak.AfterTest(c)() 40 ctx, cancel := context.WithTimeout(context.Background(), time.Second*10) 41 defer cancel() 42 43 pool := newDefaultPoolImpl(&defaultHasher{}, 4) 44 errg, ctx := errgroup.WithContext(ctx) 45 errg.Go(func() error { 46 return pool.Run(ctx) 47 }) 48 49 handle := pool.RegisterEvent(func(ctx context.Context, event interface{}) error { 50 if event.(int) == 3 { 51 return errors.New("test error") 52 } 53 return nil 54 }).OnExit(func(err error) { 55 c.Assert(err, check.ErrorMatches, "test error") 56 }) 57 58 errg.Go(func() error { 59 for i := 0; i < 10; i++ { 60 err := handle.AddEvent(ctx, i) 61 if err != nil { 62 c.Assert(err, check.ErrorMatches, ".*ErrWorkerPoolHandleCancelled.*") 63 return nil 64 } 65 } 66 return nil 67 }) 68 69 select { 70 case <-ctx.Done(): 71 c.FailNow() 72 case err := <-handle.ErrCh(): 73 c.Assert(err, check.ErrorMatches, "test error") 74 } 75 cancel() 76 77 err := errg.Wait() 78 c.Assert(err, check.ErrorMatches, "context canceled") 79 } 80 81 func (s *workerPoolSuite) TestTimerError(c *check.C) { 82 defer testleak.AfterTest(c)() 83 ctx, cancel := context.WithTimeout(context.Background(), time.Second*10) 84 defer cancel() 85 86 pool := newDefaultPoolImpl(&defaultHasher{}, 4) 87 errg, ctx := errgroup.WithContext(ctx) 88 errg.Go(func() error { 89 return pool.Run(ctx) 90 }) 91 92 counter := 0 93 handle := pool.RegisterEvent(func(ctx context.Context, event interface{}) error { 94 return nil 95 }).SetTimer(ctx, time.Millisecond*200, func(ctx context.Context) error { 96 if counter == 3 { 97 return errors.New("timer error") 98 } 99 counter++ 100 return nil 101 }) 102 103 select { 104 case <-ctx.Done(): 105 c.FailNow() 106 case err := <-handle.ErrCh(): 107 c.Assert(err, check.ErrorMatches, "timer error") 108 } 109 cancel() 110 111 err := errg.Wait() 112 c.Assert(err, check.ErrorMatches, "context canceled") 113 } 114 115 func (s *workerPoolSuite) TestMultiError(c *check.C) { 116 defer testleak.AfterTest(c)() 117 ctx, cancel := context.WithTimeout(context.Background(), time.Second*10) 118 defer cancel() 119 120 pool := newDefaultPoolImpl(&defaultHasher{}, 4) 121 errg, ctx := errgroup.WithContext(ctx) 122 errg.Go(func() error { 123 return pool.Run(ctx) 124 }) 125 126 handle := pool.RegisterEvent(func(ctx context.Context, event interface{}) error { 127 if event.(int) >= 3 { 128 return errors.New("test error") 129 } 130 return nil 131 }) 132 133 errg.Go(func() error { 134 for i := 0; i < 10; i++ { 135 err := handle.AddEvent(ctx, i) 136 if err != nil { 137 c.Assert(err, check.ErrorMatches, ".*ErrWorkerPoolHandleCancelled.*") 138 } 139 } 140 return nil 141 }) 142 143 select { 144 case <-ctx.Done(): 145 c.FailNow() 146 case err := <-handle.ErrCh(): 147 c.Assert(err, check.ErrorMatches, "test error") 148 } 149 cancel() 150 151 err := errg.Wait() 152 c.Assert(err, check.ErrorMatches, "context canceled") 153 } 154 155 func (s *workerPoolSuite) TestCancelHandle(c *check.C) { 156 defer testleak.AfterTest(c)() 157 ctx, cancel := context.WithTimeout(context.Background(), time.Second*10) 158 defer cancel() 159 160 pool := newDefaultPoolImpl(&defaultHasher{}, 4) 161 errg, ctx := errgroup.WithContext(ctx) 162 errg.Go(func() error { 163 return pool.Run(ctx) 164 }) 165 166 var num int32 167 handle := pool.RegisterEvent(func(ctx context.Context, event interface{}) error { 168 atomic.StoreInt32(&num, int32(event.(int))) 169 return nil 170 }) 171 172 errg.Go(func() error { 173 i := 0 174 for { 175 select { 176 case <-ctx.Done(): 177 return ctx.Err() 178 default: 179 } 180 err := handle.AddEvent(ctx, i) 181 if err != nil { 182 c.Assert(err, check.ErrorMatches, ".*ErrWorkerPoolHandleCancelled.*") 183 c.Assert(i, check.GreaterEqual, 5000) 184 return nil 185 } 186 i++ 187 } 188 }) 189 190 for { 191 select { 192 case <-ctx.Done(): 193 c.FailNow() 194 default: 195 } 196 if atomic.LoadInt32(&num) > 5000 { 197 break 198 } 199 } 200 201 err := failpoint.Enable("github.com/pingcap/ticdc/pkg/workerpool/addEventDelayPoint", "1*sleep(500)") 202 c.Assert(err, check.IsNil) 203 defer func() { 204 _ = failpoint.Disable("github.com/pingcap/ticdc/pkg/workerpool/addEventDelayPoint") 205 }() 206 207 handle.Unregister() 208 handle.Unregister() // Unregistering many times does not matter 209 handle.Unregister() 210 211 lastNum := atomic.LoadInt32(&num) 212 for i := 0; i <= 1000; i++ { 213 c.Assert(atomic.LoadInt32(&num), check.Equals, lastNum) 214 } 215 216 time.Sleep(1 * time.Second) 217 cancel() 218 219 err = errg.Wait() 220 c.Assert(err, check.ErrorMatches, "context canceled") 221 } 222 223 func (s *workerPoolSuite) TestCancelTimer(c *check.C) { 224 defer testleak.AfterTest(c)() 225 ctx, cancel := context.WithTimeout(context.Background(), time.Second*10) 226 defer cancel() 227 228 pool := newDefaultPoolImpl(&defaultHasher{}, 4) 229 errg, ctx := errgroup.WithContext(ctx) 230 errg.Go(func() error { 231 return pool.Run(ctx) 232 }) 233 234 err := failpoint.Enable("github.com/pingcap/ticdc/pkg/workerpool/unregisterDelayPoint", "sleep(5000)") 235 c.Assert(err, check.IsNil) 236 defer func() { 237 _ = failpoint.Disable("github.com/pingcap/ticdc/pkg/workerpool/unregisterDelayPoint") 238 }() 239 240 handle := pool.RegisterEvent(func(ctx context.Context, event interface{}) error { 241 return nil 242 }).SetTimer(ctx, 200*time.Millisecond, func(ctx context.Context) error { 243 return nil 244 }) 245 246 errg.Go(func() error { 247 i := 0 248 for { 249 err := handle.AddEvent(ctx, i) 250 if err != nil { 251 c.Assert(err, check.ErrorMatches, ".*ErrWorkerPoolHandleCancelled.*") 252 return nil 253 } 254 i++ 255 } 256 }) 257 258 handle.Unregister() 259 260 cancel() 261 err = errg.Wait() 262 c.Assert(err, check.ErrorMatches, "context canceled") 263 } 264 265 func (s *workerPoolSuite) TestTimer(c *check.C) { 266 defer testleak.AfterTest(c)() 267 ctx, cancel := context.WithTimeout(context.Background(), time.Second*10) 268 defer cancel() 269 270 pool := newDefaultPoolImpl(&defaultHasher{}, 4) 271 errg, ctx := errgroup.WithContext(ctx) 272 errg.Go(func() error { 273 return pool.Run(ctx) 274 }) 275 276 time.Sleep(200 * time.Millisecond) 277 278 handle := pool.RegisterEvent(func(ctx context.Context, event interface{}) error { 279 if event.(int) == 3 { 280 return errors.New("test error") 281 } 282 return nil 283 }) 284 285 var lastTime time.Time 286 count := 0 287 handle.SetTimer(ctx, time.Second*1, func(ctx context.Context) error { 288 if !lastTime.IsZero() { 289 c.Assert(time.Since(lastTime), check.GreaterEqual, 900*time.Millisecond) 290 c.Assert(time.Since(lastTime), check.LessEqual, 1200*time.Millisecond) 291 } 292 if count == 3 { 293 cancel() 294 return nil 295 } 296 count++ 297 298 lastTime = time.Now() 299 return nil 300 }) 301 302 err := errg.Wait() 303 c.Assert(err, check.ErrorMatches, "context canceled") 304 } 305 306 func (s *workerPoolSuite) TestBasics(c *check.C) { 307 defer testleak.AfterTest(c)() 308 309 ctx, cancel := context.WithTimeout(context.Background(), time.Second*20) 310 defer cancel() 311 312 pool := newDefaultPoolImpl(&defaultHasher{}, 4) 313 errg, ctx := errgroup.WithContext(ctx) 314 315 errg.Go(func() error { 316 return pool.Run(ctx) 317 }) 318 319 var wg sync.WaitGroup 320 321 wg.Add(16) 322 for i := 0; i < 16; i++ { 323 finalI := i 324 resultCh := make(chan int, 128) 325 handler := pool.RegisterEvent(func(ctx context.Context, event interface{}) error { 326 select { 327 case <-ctx.Done(): 328 return ctx.Err() 329 case resultCh <- event.(int): 330 } 331 log.Debug("result added", zap.Int("id", finalI), zap.Int("result", event.(int))) 332 return nil 333 }) 334 335 errg.Go(func() error { 336 for j := 0; j < 256; j++ { 337 err := handler.AddEvent(ctx, j) 338 if err != nil { 339 return errors.Trace(err) 340 } 341 } 342 return nil 343 }) 344 345 errg.Go(func() error { 346 defer wg.Done() 347 nextExpected := 0 348 for n := range resultCh { 349 select { 350 case <-ctx.Done(): 351 return ctx.Err() 352 default: 353 } 354 log.Debug("result received", zap.Int("id", finalI), zap.Int("result", n)) 355 c.Assert(n, check.Equals, nextExpected) 356 nextExpected++ 357 if nextExpected == 256 { 358 break 359 } 360 } 361 return nil 362 }) 363 } 364 365 wg.Wait() 366 cancel() 367 368 err := errg.Wait() 369 c.Assert(err, check.ErrorMatches, "context canceled") 370 } 371 372 // TestCancelByAddEventContext makes sure that the event handle can be cancelled by the context used 373 // to call `AddEvent`. 374 func (s *workerPoolSuite) TestCancelByAddEventContext(c *check.C) { 375 defer testleak.AfterTest(c)() 376 377 poolCtx, poolCancel := context.WithCancel(context.Background()) 378 defer poolCancel() 379 pool := newDefaultPoolImpl(&defaultHasher{}, 4) 380 go func() { 381 err := pool.Run(poolCtx) 382 c.Assert(err, check.ErrorMatches, ".*context canceled.*") 383 }() 384 385 ctx, cancel := context.WithTimeout(context.Background(), time.Second*20) 386 defer cancel() 387 errg, ctx := errgroup.WithContext(ctx) 388 389 for i := 0; i < 8; i++ { 390 handler := pool.RegisterEvent(func(ctx context.Context, event interface{}) error { 391 <-ctx.Done() 392 return ctx.Err() 393 }) 394 395 errg.Go(func() error { 396 for j := 0; j < 64; j++ { 397 err := handler.AddEvent(ctx, j) 398 if err != nil { 399 return nil 400 } 401 } 402 return nil 403 }) 404 405 errg.Go(func() error { 406 select { 407 case <-ctx.Done(): 408 case <-handler.ErrCh(): 409 } 410 return nil 411 }) 412 } 413 414 time.Sleep(5 * time.Second) 415 cancel() 416 417 err := errg.Wait() 418 c.Assert(err, check.IsNil) 419 } 420 421 // Benchmark workerpool with ping-pong workflow. 422 // go test -benchmem -run='^$' -bench '^(BenchmarkWorkerpool)$' github.com/pingcap/ticdc/pkg/workerpool 423 func BenchmarkWorkerpool(b *testing.B) { 424 ctx, cancel := context.WithCancel(context.Background()) 425 defer cancel() 426 427 pool := newDefaultPoolImpl(&defaultHasher{}, 4) 428 go func() { _ = pool.Run(ctx) }() 429 430 ch := make(chan int) 431 handler := pool.RegisterEvent(func(ctx context.Context, event interface{}) error { 432 ch <- event.(int) 433 return nil 434 }) 435 436 b.ResetTimer() 437 for i := 0; i < b.N; i++ { 438 err := handler.AddEvent(ctx, i) 439 if err != nil { 440 b.Fatal(err) 441 } 442 <-ch 443 } 444 }