github.com/rudderlabs/rudder-go-kit@v0.30.0/sync/limiter_test.go (about)

     1  package sync_test
     2  
     3  import (
     4  	"context"
     5  	"strconv"
     6  	"sync"
     7  	"testing"
     8  	"time"
     9  
    10  	"github.com/stretchr/testify/require"
    11  
    12  	"github.com/rudderlabs/rudder-go-kit/stats"
    13  	"github.com/rudderlabs/rudder-go-kit/stats/memstats"
    14  	miscsync "github.com/rudderlabs/rudder-go-kit/sync"
    15  )
    16  
    17  func TestLimiter(t *testing.T) {
    18  	t.Run("without priority", func(t *testing.T) {
    19  		ctx, cancel := context.WithCancel(context.Background())
    20  		var wg sync.WaitGroup
    21  		ms, err := memstats.New()
    22  		require.NoError(t, err)
    23  
    24  		statsTriggerCh := make(chan time.Time)
    25  		triggerFn := func() <-chan time.Time {
    26  			return statsTriggerCh
    27  		}
    28  
    29  		limiter := miscsync.NewLimiter(ctx, &wg, "test", 1, ms, miscsync.WithLimiterStatsTriggerFunc(triggerFn))
    30  		var counter int
    31  		statsTriggerCh <- time.Now()
    32  		statsTriggerCh <- time.Now()
    33  
    34  		require.NotNil(t, ms.Get("test_limiter_active_routines", nil))
    35  		require.EqualValues(t, 0, ms.Get("test_limiter_active_routines", nil).LastValue(), "shouldn't have any active")
    36  
    37  		require.NotNil(t, ms.Get("test_limiter_availability", nil))
    38  		require.EqualValues(t, 1, ms.Get("test_limiter_availability", nil).LastValue(), "should be available")
    39  
    40  		require.Nil(t, ms.Get("test_limiter_working", nil))
    41  
    42  		for i := 0; i < 100; i++ {
    43  			wg.Add(1)
    44  			key := strconv.Itoa(i)
    45  			go func() {
    46  				limiter.Do(key, func() {
    47  					counter++ // since the limiter's limit is 1, we shouldn't need an atomic counter
    48  					wg.Done()
    49  				})
    50  			}()
    51  		}
    52  
    53  		cancel()
    54  		wg.Wait()
    55  
    56  		require.EqualValues(t, 100, counter, "counter should be 100")
    57  
    58  		select {
    59  		case statsTriggerCh <- time.Now():
    60  			require.Fail(t, "shouldn't be listening to triggerCh anymore")
    61  		default:
    62  		}
    63  		for i := 0; i < 100; i++ {
    64  			m := ms.Get("test_limiter_working", map[string]string{"key": strconv.Itoa(i)})
    65  			require.NotNil(t, m)
    66  			require.Lenf(t, m.Durations(), 1, "should have recorded 1 timer duration for key %d", i)
    67  		}
    68  	})
    69  
    70  	t.Run("with priority", func(t *testing.T) {
    71  		ctx, cancel := context.WithCancel(context.Background())
    72  		var wg sync.WaitGroup
    73  		ms, err := memstats.New()
    74  		require.NoError(t, err)
    75  
    76  		limiter := miscsync.NewLimiter(ctx, &wg, "test", 1, ms)
    77  		var counterLow int
    78  		var counterHigh int
    79  		sleepTime := 100 * time.Microsecond
    80  		for i := 0; i < 1000; i++ {
    81  			wg.Add(1)
    82  			key := strconv.Itoa(i)
    83  			go func() {
    84  				limiter.DoWithPriority(key, miscsync.LimiterPriorityValueHigh, func() {
    85  					time.Sleep(sleepTime)
    86  					counterHigh++ // since the limiter's limit is 1, we shouldn't need an atomic counter
    87  					require.Equal(t, 0, counterLow, "counterLow should be 0")
    88  					wg.Done()
    89  				})
    90  			}()
    91  		}
    92  
    93  		time.Sleep(10 * sleepTime)
    94  		for i := 0; i < 1000; i++ {
    95  			wg.Add(1)
    96  			key := strconv.Itoa(i)
    97  			go func() {
    98  				limiter.DoWithPriority(key, miscsync.LimiterPriorityValueLow, func() {
    99  					counterLow++ // since the limiter's limit is 1, we shouldn't need an atomic counter
   100  					wg.Done()
   101  				})
   102  			}()
   103  		}
   104  
   105  		cancel()
   106  		wg.Wait()
   107  
   108  		require.EqualValues(t, 1000, counterHigh, "high priority counter should be 1000")
   109  		require.EqualValues(t, 1000, counterLow, "low priority counter should be 1000")
   110  	})
   111  
   112  	t.Run("with dynamic priority", func(t *testing.T) {
   113  		ctx, cancel := context.WithCancel(context.Background())
   114  		var wg sync.WaitGroup
   115  		ms, err := memstats.New()
   116  		require.NoError(t, err)
   117  
   118  		sleepTime := 1 * time.Millisecond
   119  		limiter := miscsync.NewLimiter(ctx, &wg, "test", 1, ms, miscsync.WithLimiterDynamicPeriod(sleepTime/100))
   120  		var counterLow int
   121  		var counterHigh int
   122  
   123  		var dynamicPriorityVerified bool
   124  		for i := 0; i < 1000; i++ {
   125  			wg.Add(1)
   126  			key := strconv.Itoa(i)
   127  			go func() {
   128  				limiter.DoWithPriority(key, miscsync.LimiterPriorityValueHigh, func() {
   129  					time.Sleep(sleepTime)
   130  					counterHigh++ // since the limiter's limit is 1, we shouldn't need an atomic counter
   131  					if counterLow > 0 {
   132  						dynamicPriorityVerified = true
   133  					}
   134  					wg.Done()
   135  				})
   136  			}()
   137  		}
   138  
   139  		for i := 0; i < 10; i++ {
   140  			wg.Add(1)
   141  			key := strconv.Itoa(i)
   142  			go func() {
   143  				limiter.DoWithPriority(key, miscsync.LimiterPriorityValueLow, func() {
   144  					counterLow++ // since the limiter's limit is 1, we shouldn't need an atomic counter
   145  					wg.Done()
   146  				})
   147  			}()
   148  		}
   149  
   150  		cancel()
   151  		wg.Wait()
   152  
   153  		require.True(t, dynamicPriorityVerified, "dynamic priority should have been verified")
   154  		require.EqualValues(t, 1000, counterHigh, "high priority counter should be 1000")
   155  		require.EqualValues(t, 10, counterLow, "low priority counter should be 10")
   156  	})
   157  
   158  	t.Run("invalid limit", func(t *testing.T) {
   159  		require.Panics(t, func() {
   160  			var wg sync.WaitGroup
   161  			_ = miscsync.NewLimiter(context.Background(), &wg, "zerolimit", 0, stats.Default)
   162  		})
   163  
   164  		require.Panics(t, func() {
   165  			var wg sync.WaitGroup
   166  			_ = miscsync.NewLimiter(context.Background(), &wg, "negativelimit", -1, stats.Default)
   167  		})
   168  	})
   169  }