github.com/m3db/m3@v1.5.1-0.20231129193456-75a402aa583b/src/x/sync/pooled_worker_pool_test.go (about)

     1  // Copyright (c) 2018 Uber Technologies, Inc.
     2  //
     3  // Permission is hereby granted, free of charge, to any person obtaining a copy
     4  // of this software and associated documentation files (the "Software"), to deal
     5  // in the Software without restriction, including without limitation the rights
     6  // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
     7  // copies of the Software, and to permit persons to whom the Software is
     8  // furnished to do so, subject to the following conditions:
     9  //
    10  // The above copyright notice and this permission notice shall be included in
    11  // all copies or substantial portions of the Software.
    12  //
    13  // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    14  // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    15  // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    16  // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    17  // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    18  // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
    19  // THE SOFTWARE.
    20  
    21  package sync
    22  
    23  import (
    24  	"context"
    25  	"sync"
    26  	"sync/atomic"
    27  	"testing"
    28  	"time"
    29  
    30  	"github.com/stretchr/testify/assert"
    31  	"github.com/stretchr/testify/require"
    32  )
    33  
    34  func TestPooledWorkerPoolGo(t *testing.T) {
    35  	var count uint32
    36  
    37  	p, err := NewPooledWorkerPool(testWorkerPoolSize, NewPooledWorkerPoolOptions())
    38  	require.NoError(t, err)
    39  	p.Init()
    40  
    41  	var wg sync.WaitGroup
    42  	for i := 0; i < testWorkerPoolSize*2; i++ {
    43  		wg.Add(1)
    44  		p.Go(func() {
    45  			atomic.AddUint32(&count, 1)
    46  			wg.Done()
    47  		})
    48  	}
    49  	wg.Wait()
    50  
    51  	require.Equal(t, uint32(testWorkerPoolSize*2), count)
    52  }
    53  
    54  func TestPooledWorkerPoolGoWithContext(t *testing.T) {
    55  	ctx, cancel := context.WithCancel(context.Background())
    56  	defer cancel()
    57  
    58  	wp, err := NewPooledWorkerPool(testWorkerPoolSize,
    59  		NewPooledWorkerPoolOptions().SetGrowOnDemand(false))
    60  	require.NoError(t, err)
    61  	wp.Init()
    62  
    63  	// Cancel and make sure worker will prefer to return from canceled
    64  	// work rather than always enqueue.
    65  	cancel()
    66  
    67  	var aborted uint32
    68  	for i := 0; i < 100; i++ {
    69  		go func() {
    70  			result := wp.GoWithContext(ctx, func() {
    71  				time.Sleep(time.Second)
    72  			})
    73  			if !result {
    74  				atomic.AddUint32(&aborted, 1)
    75  			}
    76  		}()
    77  	}
    78  
    79  	n := atomic.LoadUint32(&aborted)
    80  	require.True(t, n > 0)
    81  	t.Logf("aborted: %d", n)
    82  }
    83  
    84  func TestPooledWorkerPoolGoWithTimeout(t *testing.T) {
    85  	var (
    86  		workers = 2
    87  		opts    = NewPooledWorkerPoolOptions().SetNumShards(int64(workers))
    88  	)
    89  	p, err := NewPooledWorkerPool(workers, opts)
    90  	require.NoError(t, err)
    91  	p.Init()
    92  
    93  	pooledWorkerPool, ok := p.(*pooledWorkerPool)
    94  	require.True(t, ok)
    95  
    96  	// First fill up all the queues without blocking.
    97  	var wg sync.WaitGroup
    98  	wg.Add(1)
    99  
   100  	// Enqueue workers * 2 since buffered channel will allow workers / shards
   101  	// (which is 1, since 2 / 2 = 1) which means we need to enqueue two times
   102  	// the workers.
   103  	totalEnqueue := workers * 2
   104  	now := time.Now()
   105  	for i := 0; i < totalEnqueue; i++ {
   106  		// Set now in such a way that independent shards are selected.
   107  		shardNowSelect := now.
   108  			Truncate(time.Duration(totalEnqueue) * time.Nanosecond).
   109  			Add(time.Duration(i) * time.Nanosecond)
   110  		pooledWorkerPool.nowFn = func() time.Time {
   111  			return shardNowSelect
   112  		}
   113  
   114  		result := p.GoWithTimeout(func() {
   115  			wg.Wait()
   116  		}, 100*time.Millisecond)
   117  		assert.True(t, result)
   118  	}
   119  
   120  	// Restore the now fn.
   121  	pooledWorkerPool.nowFn = time.Now
   122  
   123  	// Now ensure all further enqueues time out.
   124  	for i := 0; i < workers; i++ {
   125  		result := p.GoWithTimeout(func() {
   126  			wg.Wait()
   127  		}, 100*time.Millisecond)
   128  		assert.False(t, result)
   129  	}
   130  
   131  	// Release goroutines.
   132  	wg.Done()
   133  }
   134  
   135  func TestPooledWorkerPoolGrowOnDemand(t *testing.T) {
   136  	var count uint32
   137  
   138  	p, err := NewPooledWorkerPool(
   139  		1,
   140  		NewPooledWorkerPoolOptions().
   141  			SetGrowOnDemand(true))
   142  	require.NoError(t, err)
   143  	p.Init()
   144  
   145  	var (
   146  		wg       sync.WaitGroup
   147  		numIters = testWorkerPoolSize * 2
   148  		doneCh   = make(chan struct{})
   149  	)
   150  	wg.Add(numIters)
   151  
   152  	for i := 0; i < numIters; i++ {
   153  		// IfGoOrGrow did not allocate new goroutines then
   154  		// this test would never complete this loop as the
   155  		// anonymous Work function below would not complete
   156  		// and would block further iterations.
   157  		p.Go(func() {
   158  			atomic.AddUint32(&count, 1)
   159  			wg.Done()
   160  			<-doneCh
   161  		})
   162  	}
   163  	close(doneCh)
   164  	wg.Wait()
   165  
   166  	require.Equal(t, uint32(numIters), count)
   167  }
   168  
   169  func TestPooledWorkerPoolGoOrGrowKillWorker(t *testing.T) {
   170  	var count uint32
   171  
   172  	p, err := NewPooledWorkerPool(
   173  		1,
   174  		NewPooledWorkerPoolOptions().
   175  			SetGrowOnDemand(true).
   176  			SetKillWorkerProbability(1.0))
   177  	require.NoError(t, err)
   178  	p.Init()
   179  
   180  	var (
   181  		wg       sync.WaitGroup
   182  		numIters = testWorkerPoolSize * 2
   183  		doneCh   = make(chan struct{})
   184  	)
   185  	wg.Add(numIters)
   186  
   187  	for i := 0; i < numIters; i++ {
   188  		// IfGoOrGrow did not allocate new goroutines then
   189  		// this test would never complete this loop as the
   190  		// anonymous Work function below would not complete
   191  		// and would block further iterations.
   192  		p.Go(func() {
   193  			atomic.AddUint32(&count, 1)
   194  			wg.Done()
   195  			<-doneCh
   196  		})
   197  	}
   198  	close(doneCh)
   199  	wg.Wait()
   200  
   201  	require.Equal(t, uint32(numIters), count)
   202  }
   203  
   204  func TestPooledWorkerPoolGoKillWorker(t *testing.T) {
   205  	var count uint32
   206  
   207  	p, err := NewPooledWorkerPool(
   208  		testWorkerPoolSize,
   209  		NewPooledWorkerPoolOptions().
   210  			SetKillWorkerProbability(1.0))
   211  	require.NoError(t, err)
   212  	p.Init()
   213  
   214  	var wg sync.WaitGroup
   215  	for i := 0; i < testWorkerPoolSize*2; i++ {
   216  		wg.Add(1)
   217  		p.Go(func() {
   218  			atomic.AddUint32(&count, 1)
   219  			wg.Done()
   220  		})
   221  	}
   222  	wg.Wait()
   223  
   224  	require.Equal(t, uint32(testWorkerPoolSize*2), count)
   225  }
   226  
   227  func TestPooledWorkerPoolSizeTooSmall(t *testing.T) {
   228  	_, err := NewPooledWorkerPool(0, NewPooledWorkerPoolOptions())
   229  	require.Error(t, err)
   230  }
   231  
   232  func TestPooledWorkerFast(t *testing.T) {
   233  	wp, err := NewPooledWorkerPool(1, NewPooledWorkerPoolOptions())
   234  	require.NoError(t, err)
   235  	wp.Init()
   236  
   237  	fast := wp.FastContextCheck(3)
   238  
   239  	ctx, cancel := context.WithCancel(context.Background())
   240  	cancel()
   241  
   242  	require.False(t, fast.GoWithContext(ctx, func() {}))
   243  	require.True(t, fast.GoWithContext(ctx, func() {}))
   244  	require.True(t, fast.GoWithContext(ctx, func() {}))
   245  	require.False(t, fast.GoWithContext(ctx, func() {}))
   246  	require.True(t, fast.GoWithContext(ctx, func() {}))
   247  }