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  }