github.com/Aoi-hosizora/ahlib@v1.5.1-0.20230404072829-241b93cf91c7/xgopool/xgopool_test.go (about)

     1  package xgopool
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"github.com/Aoi-hosizora/ahlib/xtesting"
     7  	"runtime"
     8  	"sync"
     9  	"sync/atomic"
    10  	"testing"
    11  )
    12  
    13  func TestSimpleGo(t *testing.T) {
    14  	t.Run("cap", func(t *testing.T) {
    15  		xtesting.Panic(t, func() { New(0) })
    16  		xtesting.Panic(t, func() { New(-1) })
    17  		xtesting.NotPanic(t, func() { New(1) })
    18  
    19  		serializedPool := New(1)
    20  		xtesting.Equal(t, serializedPool.WorkersCap(), int32(1))
    21  		xtesting.Panic(t, func() { serializedPool.SetWorkersCap(0) })
    22  		xtesting.Panic(t, func() { serializedPool.SetWorkersCap(-1) })
    23  		xtesting.NotPanic(t, func() { serializedPool.SetWorkersCap(2) })
    24  		xtesting.Equal(t, serializedPool.WorkersCap(), int32(2))
    25  		xtesting.NotPanic(t, func() { serializedPool.SetWorkersCap(100) })
    26  		xtesting.Equal(t, serializedPool.WorkersCap(), int32(100))
    27  
    28  		xtesting.Equal(t, WorkersCap(), int32(10000))
    29  		SetWorkersCap(100)
    30  		xtesting.Equal(t, WorkersCap(), int32(100))
    31  		SetWorkersCap(10000)
    32  		xtesting.Equal(t, WorkersCap(), int32(10000))
    33  		xtesting.Equal(t, NumWorkers(), int32(0))
    34  		xtesting.Equal(t, NumTasks(), int32(0))
    35  	})
    36  
    37  	t.Run("incr", func(t *testing.T) {
    38  		wg := sync.WaitGroup{}
    39  		tmp := 0
    40  		incr := func() { tmp++; wg.Done() }
    41  
    42  		wg.Add(1)
    43  		Go(incr)
    44  		wg.Wait()
    45  		xtesting.Equal(t, tmp, 1)
    46  		//
    47  		wg.Add(1)
    48  		Go(incr)
    49  		wg.Wait()
    50  		xtesting.Equal(t, tmp, 2)
    51  		//
    52  		numTasks := _defaultPool.numTasks
    53  		Go(nil)
    54  		xtesting.Equal(t, _defaultPool.numTasks, numTasks)
    55  		xtesting.Equal(t, tmp, 2)
    56  		//
    57  		wg.Add(1)
    58  		Go(incr)
    59  		wg.Wait()
    60  		xtesting.Equal(t, tmp, 3)
    61  
    62  		xtesting.Equal(t, NumWorkers(), int32(0))
    63  		xtesting.Equal(t, NumTasks(), int32(0))
    64  	})
    65  
    66  	t.Run("decr", func(t *testing.T) {
    67  		wg := sync.WaitGroup{}
    68  		tmp := 3
    69  		decr := func(_ context.Context) { tmp--; wg.Done() }
    70  
    71  		wg.Add(1)
    72  		CtxGo(context.Background(), decr)
    73  		wg.Wait()
    74  		xtesting.Equal(t, tmp, 2)
    75  		//
    76  		wg.Add(1)
    77  		CtxGo(context.Background(), decr)
    78  		wg.Wait()
    79  		xtesting.Equal(t, tmp, 1)
    80  		//
    81  		numTasks := _defaultPool.numTasks
    82  		CtxGo(context.Background(), nil)
    83  		xtesting.Equal(t, _defaultPool.numTasks, numTasks)
    84  		xtesting.Equal(t, tmp, 1)
    85  		//
    86  		wg.Add(1)
    87  		CtxGo(context.Background(), decr)
    88  		wg.Wait()
    89  		xtesting.Equal(t, tmp, 0)
    90  
    91  		xtesting.Equal(t, NumWorkers(), int32(0))
    92  		xtesting.Equal(t, NumTasks(), int32(0))
    93  	})
    94  
    95  	t.Run("atomic add", func(t *testing.T) {
    96  		wg := sync.WaitGroup{}
    97  		p := New(100)
    98  		n := int32(0)
    99  		for i := 0; i < 2000; i++ {
   100  			wg.Add(1)
   101  			p.Go(func() {
   102  				atomic.AddInt32(&n, 1)
   103  				wg.Done()
   104  			})
   105  		}
   106  		wg.Wait()
   107  		xtesting.Equal(t, n, int32(2000))
   108  
   109  		p.SetWorkersCap(2000)
   110  		n = 0
   111  		for i := 0; i < 2000; i++ {
   112  			wg.Add(1)
   113  			p.Go(func() {
   114  				atomic.AddInt32(&n, 1)
   115  				wg.Done()
   116  			})
   117  		}
   118  		wg.Wait()
   119  		xtesting.Equal(t, n, int32(2000))
   120  	})
   121  }
   122  
   123  func TestPanicGo(t *testing.T) {
   124  	terminated := make(chan struct{})
   125  	done := func() { terminated <- struct{}{} }
   126  	wait := func() { <-terminated }
   127  
   128  	t.Run("default panic handler", func(t *testing.T) {
   129  		originHandler := _defaultPool.panicHandler
   130  		SetPanicHandler(func(ctx context.Context, i interface{}) {
   131  			originHandler(ctx, i)
   132  			done()
   133  		})
   134  		defer SetPanicHandler(originHandler)
   135  		Go(func() { panic("panic") })
   136  		// Warning: Panic with `panic`
   137  		wait()
   138  	})
   139  
   140  	t.Run("panic with no panic handler 1", func(t *testing.T) {
   141  		g := New(100)
   142  		g.SetPanicHandler(nil)
   143  		g.Go(func() {
   144  			defer done()
   145  			defer func() { err := recover(); xtesting.Equal(t, err, "panic") }()
   146  			panic("panic")
   147  		})
   148  		wait()
   149  	})
   150  
   151  	t.Run("panic with no panic handler 2", func(t *testing.T) {
   152  		g := New(100)
   153  		g.SetPanicHandler(nil)
   154  		_testFlag.Store(true) // setup test flag
   155  		g.Go(func() {
   156  			panic("panic for testing ...")
   157  		})
   158  		// Panic when testing: `panic for testing ...`
   159  		for _testFlag.Load() == true { // wait for the panic handler to finish
   160  		}
   161  	})
   162  
   163  	t.Run("panic with msg panic handler", func(t *testing.T) {
   164  		g := New(100)
   165  		msg := new(string)
   166  		g.SetPanicHandler(func(_ context.Context, i interface{}) {
   167  			*msg = fmt.Sprintf("%v_%v", i, i)
   168  			done()
   169  		})
   170  		g.Go(func() { panic("panic") })
   171  		wait()
   172  		xtesting.Equal(t, *msg, "panic_panic")
   173  	})
   174  
   175  	t.Run("panic with context panic handler", func(t *testing.T) {
   176  		g := New(100)
   177  		msg := new(string)
   178  		g.SetPanicHandler(func(ctx context.Context, i interface{}) {
   179  			*msg = fmt.Sprintf("%v_%v", i, ctx.Value("id"))
   180  			done()
   181  		})
   182  		g.CtxGo(context.WithValue(context.Background(), "id", 333), func(ctx context.Context) {
   183  			xtesting.Equal(t, ctx.Value("id"), 333)
   184  			panic("panic")
   185  		})
   186  		wait()
   187  		xtesting.Equal(t, *msg, "panic_333")
   188  	})
   189  }
   190  
   191  func TestWorkersCap(t *testing.T) {
   192  	const N = 5000
   193  	integers := new([]int)
   194  	matchedIntegers := make([]int, 0, N)
   195  	for i := 0; i < N; i++ {
   196  		matchedIntegers = append(matchedIntegers, i)
   197  	}
   198  
   199  	t.Run("serialize", func(t *testing.T) {
   200  		*integers = make([]int, 0, N)
   201  		serializedPool := New(1)
   202  		for i := 0; i < N; i++ {
   203  			i := i
   204  			serializedPool.Go(func() {
   205  				xtesting.Equal(t, serializedPool.NumWorkers(), int32(1))
   206  				*integers = append(*integers, i)
   207  			})
   208  		}
   209  		terminated := make(chan struct{})
   210  		serializedPool.Go(func() {
   211  			xtesting.Equal(t, *integers, matchedIntegers)
   212  			close(terminated)
   213  		})
   214  		<-terminated
   215  	})
   216  
   217  	t.Run("serialized parallel", func(t *testing.T) {
   218  		*integers = make([]int, 0, N)
   219  		parallelizedPool := New(2)
   220  		terminated := make(chan struct{})
   221  		for i := 0; i < N; i++ {
   222  			i := i
   223  			parallelizedPool.Go(func() {
   224  				xtesting.True(t, parallelizedPool.NumWorkers() <= 2)
   225  				xtesting.Equal(t, parallelizedPool.NumTasks(), int32(0))
   226  				*integers = append(*integers, i)
   227  				terminated <- struct{}{}
   228  			})
   229  			<-terminated
   230  		}
   231  		close(terminated)
   232  		xtesting.Equal(t, *integers, matchedIntegers)
   233  	})
   234  
   235  	t.Run("parallel", func(t *testing.T) {
   236  		*integers = make([]int, 0, N)
   237  		parallelizedPool := New(5)
   238  		mu := sync.Mutex{}
   239  		wg := sync.WaitGroup{}
   240  		for i := 0; i < N; i++ {
   241  			i := i
   242  			wg.Add(1)
   243  			parallelizedPool.Go(func() {
   244  				mu.Lock()
   245  				*integers = append(*integers, i)
   246  				mu.Unlock()
   247  				wg.Done()
   248  			})
   249  		}
   250  		wg.Wait()
   251  		xtesting.ElementMatch(t, *integers, matchedIntegers)
   252  	})
   253  }
   254  
   255  // benchmarks are referred from https://github.com/bytedance/gopkg/blob/develop/util/gopool/pool_test.go.
   256  
   257  const benchmarkTimes = 20000
   258  
   259  func doCopyStack(_, b int) int {
   260  	if b < 100 {
   261  		return doCopyStack(0, b+1)
   262  	}
   263  	return 0
   264  }
   265  
   266  func testFunc() {
   267  	doCopyStack(0, 0)
   268  }
   269  
   270  func BenchmarkPool(b *testing.B) {
   271  	b.Run("GoPool_xgopool", func(b *testing.B) {
   272  		p := New(int32(runtime.GOMAXPROCS(0)))
   273  		var wg sync.WaitGroup
   274  		b.ReportAllocs()
   275  		b.ResetTimer()
   276  		for i := 0; i < b.N; i++ {
   277  			wg.Add(benchmarkTimes)
   278  			for j := 0; j < benchmarkTimes; j++ {
   279  				p.CtxGo(context.Background(), func(_ context.Context) {
   280  					testFunc()
   281  					wg.Done()
   282  				})
   283  			}
   284  			wg.Wait()
   285  		}
   286  	})
   287  	// "github.com/bytedance/gopkg/util/gopool"
   288  	// b.Run("GoPool_gopkg", func(b *testing.B) {
   289  	// 	p := gopool.NewPool("", int32(runtime.GOMAXPROCS(0)), gopool.NewConfig())
   290  	// 	var wg sync.WaitGroup
   291  	// 	b.ReportAllocs()
   292  	// 	b.ResetTimer()
   293  	// 	for i := 0; i < b.N; i++ {
   294  	// 		wg.Add(benchmarkTimes)
   295  	// 		for j := 0; j < benchmarkTimes; j++ {
   296  	// 			p.Go(func() {
   297  	// 				testFunc()
   298  	// 				wg.Done()
   299  	// 			})
   300  	// 		}
   301  	// 		wg.Wait()
   302  	// 	}
   303  	// })
   304  	b.Run("StdGo", func(b *testing.B) {
   305  		var wg sync.WaitGroup
   306  		b.ReportAllocs()
   307  		b.ResetTimer()
   308  		for i := 0; i < b.N; i++ {
   309  			wg.Add(benchmarkTimes)
   310  			for j := 0; j < benchmarkTimes; j++ {
   311  				go func() {
   312  					testFunc()
   313  					wg.Done()
   314  				}()
   315  			}
   316  			wg.Wait()
   317  		}
   318  	})
   319  }
   320  
   321  /*
   322  	goos: windows
   323  	goarch: amd64
   324  	pkg: github.com/Aoi-hosizora/ahlib/xgopool
   325  	cpu: Intel(R) Core(TM) i5-8250U CPU @ 1.60GHz
   326  	BenchmarkPool
   327  	BenchmarkPool/GoPool_xgopool
   328  	BenchmarkPool/GoPool_xgopool-8         	      52	  20352171 ns/op	  383157 B/op	   22036 allocs/op
   329  	BenchmarkPool/GoPool_gopkg
   330  	BenchmarkPool/GoPool_gopkg-8           	     128	  18193410 ns/op	  367304 B/op	   22530 allocs/op
   331  	BenchmarkPool/StdGo
   332  	BenchmarkPool/StdGo-8                  	      33	  70946273 ns/op	  320000 B/op	   20000 allocs/op
   333  	PASS
   334  */