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  }