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 }