github.com/Schaudge/grailbase@v0.0.0-20240223061707-44c758a471c0/admit/admit_test.go (about) 1 // Copyright 2018 GRAIL, Inc. All rights reserved. 2 // Use of this source code is governed by the Apache-2.0 3 // license that can be found in the LICENSE file. 4 5 package admit 6 7 import ( 8 "context" 9 "expvar" 10 "fmt" 11 "math/rand" 12 "sync" 13 "sync/atomic" 14 "testing" 15 "time" 16 17 "github.com/Schaudge/grailbase/retry" 18 "github.com/Schaudge/grailbase/traverse" 19 ) 20 21 func checkState(t *testing.T, p Policy, limit, used int) { 22 t.Helper() 23 var gotl, gotu int 24 switch c := p.(type) { 25 case *controller: 26 gotl = c.limit 27 gotu = c.used 28 case *aimd: 29 gotl = c.limit 30 gotu = c.used 31 } 32 if gotu != used { 33 t.Errorf("c.used: got %d, want %d", gotu, used) 34 } 35 if gotl != limit { 36 t.Errorf("c.limit: got %d, want %d", gotl, limit) 37 } 38 } 39 40 func checkVars(t *testing.T, key, max, used string) { 41 t.Helper() 42 if want, got := max, admitLimit.Get(key).String(); got != want { 43 t.Errorf("admitLimit got %s, want %s", got, want) 44 } 45 if want, got := used, admitUsed.Get(key).String(); got != want { 46 t.Errorf("admitUsed got %s, want %s", got, want) 47 } 48 } 49 50 func getKeys(m *expvar.Map) map[string]bool { 51 keys := make(map[string]bool) 52 m.Do(func(kv expvar.KeyValue) { 53 keys[kv.Key] = true 54 }) 55 return keys 56 } 57 58 func TestController(t *testing.T) { 59 c := newController(10, 15) 60 // use up 5. 61 if err := c.Acquire(context.Background(), 5); err != nil { 62 t.Fatal(err) 63 } 64 checkState(t, c, 10, 5) 65 // can go upto 6. 66 if err := c.Acquire(context.Background(), 6); err != nil { 67 t.Fatal(err) 68 } 69 // release and report capacity error. 70 c.Release(5, false) 71 checkState(t, c, 10, 6) 72 ctx, cancel := context.WithTimeout(context.Background(), time.Millisecond) 73 // 6 still in use and limit should now be 10, so can't acquire 6. 74 if want, got := context.DeadlineExceeded, c.Acquire(ctx, 6); got != want { 75 t.Fatalf("got %v, want %v", got, want) 76 } 77 cancel() 78 if want, got := 0, getKeys(admitLimit); len(got) != want { 79 t.Fatalf("admitLimit got %v, want len %d", got, want) 80 } 81 if want, got := 0, getKeys(admitUsed); len(got) != want { 82 t.Fatalf("admitUsed got %v, want len %d", got, want) 83 } 84 EnableVarExport(c, "test") 85 c.Release(6, true) 86 checkState(t, c, 10, 0) 87 checkVars(t, "test", "10", "0") 88 // max is still 9, but since none are used, should accommodate larger request. 89 if err := c.Acquire(context.Background(), 18); err != nil { 90 t.Fatal(err) 91 } 92 checkState(t, c, 10, 18) 93 checkVars(t, "test", "10", "18") 94 c.Release(17, true) 95 checkState(t, c, 15, 1) 96 checkVars(t, "test", "15", "1") 97 ctx, cancel = context.WithTimeout(context.Background(), time.Millisecond) 98 // 1 still in use and max is 15, so shouldn't accommodate larger request. 99 if want, got := context.DeadlineExceeded, c.Acquire(ctx, 18); got != want { 100 t.Fatalf("got %v, want %v", got, want) 101 } 102 cancel() 103 checkState(t, c, 15, 1) 104 checkVars(t, "test", "15", "1") 105 c.Release(1, true) 106 checkState(t, c, 15, 0) 107 checkVars(t, "test", "15", "0") 108 } 109 110 func TestControllerConcurrently(t *testing.T) { 111 testPolicy(t, ControllerWithRetry(100, 1000, nil)) 112 } 113 114 func TestAIMD(t *testing.T) { 115 c := newAimd(10, 0.2) 116 // use up 5. 117 if err := c.Acquire(context.Background(), 5); err != nil { 118 t.Fatal(err) 119 } 120 checkState(t, c, 10, 5) 121 ctx, cancel := context.WithTimeout(context.Background(), time.Millisecond) 122 // 5 in use and limit should still be 10, so can't acquire 6. 123 if want, got := context.DeadlineExceeded, c.Acquire(ctx, 6); got != want { 124 t.Fatalf("got %v, want %v", got, want) 125 } 126 cancel() 127 // release and report capacity error. 128 EnableVarExport(c, "aimd") 129 c.Release(5, true) 130 checkState(t, c, 10, 0) 131 checkVars(t, "aimd", "10", "0") 132 133 for i := 0; i < 10; i++ { 134 if err := c.Acquire(context.Background(), 1); err != nil { 135 t.Fatal(err) 136 } 137 } 138 checkState(t, c, 10, 10) 139 checkVars(t, "aimd", "10", "10") 140 for i := 1; i <= 5; i++ { 141 c.Release(i, true) 142 if err := c.Acquire(context.Background(), i+1); err != nil { 143 t.Fatal(err) 144 } 145 } 146 checkState(t, c, 15, 15) 147 checkVars(t, "aimd", "15", "15") 148 149 c.Release(1, false) 150 checkState(t, c, 12, 14) 151 checkVars(t, "aimd", "12", "14") 152 153 c.Release(1, false) 154 checkState(t, c, 10, 13) 155 checkVars(t, "aimd", "10", "13") 156 157 ctx, cancel = context.WithTimeout(context.Background(), time.Millisecond) 158 // 13 still in use and limit should now be 10, so can't acquire 1. 159 if want, got := context.DeadlineExceeded, c.Acquire(ctx, 1); got != want { 160 t.Fatalf("got %v, want %v", got, want) 161 } 162 cancel() 163 } 164 165 func TestAIMDConcurrently(t *testing.T) { 166 testPolicy(t, AIMDWithRetry(100, 0.25, nil)) 167 } 168 169 func testPolicy(t *testing.T, p Policy) { 170 const ( 171 N = 100 172 T = 100 173 ) 174 var pending int32 175 var begin sync.WaitGroup 176 begin.Add(N) 177 err := traverse.Each(N, func(i int) error { 178 begin.Done() 179 n := rand.Intn(T/10) + 1 180 if err := p.Acquire(context.Background(), n); err != nil { 181 return err 182 } 183 if m := atomic.AddInt32(&pending, int32(n)); m > T { 184 return fmt.Errorf("too many tokens: %d > %d", m, T) 185 } 186 atomic.AddInt32(&pending, -int32(n)) 187 p.Release(n, (i > 10 && i < 20) || (i > 70 && i < 80)) 188 return nil 189 }) 190 if err != nil { 191 t.Fatal(err) 192 } 193 } 194 195 func TestDo(t *testing.T) { 196 c := newController(100, 10000) 197 // Must satisfy even 150 tokens since none are used. 198 if err := Do(context.Background(), c, 150, func() (bool, error) { return true, nil }); err != nil { 199 t.Fatal(err) 200 } 201 checkState(t, c, 150, 0) 202 // controller has 150 tokens, use 10 and report capacity error 203 if want, got := error(nil), Do(context.Background(), c, 10, func() (bool, error) { return false, nil }); got != want { 204 t.Fatalf("got %v, want %v", got, want) 205 } 206 checkState(t, c, 135, 0) 207 // controller has 135 tokens, use up 35... 208 c.Acquire(context.Background(), 35) 209 checkState(t, c, 135, 35) 210 // can go upto 1.1*135 = 148, so should timeout for 114. 211 ctx, cancel := context.WithTimeout(context.Background(), time.Second) 212 defer cancel() 213 if want, got := context.DeadlineExceeded, Do(ctx, c, 114, func() (bool, error) { return true, nil }); got != want { 214 t.Fatalf("got %v, want %v", got, want) 215 } 216 checkState(t, c, 135, 35) 217 // can go upto 1.1*135 = 148, so should timeout for 113. 218 if want, got := error(nil), Do(context.Background(), c, 113, func() (bool, error) { return true, nil }); got != want { 219 t.Fatalf("got %v, want %v", got, want) 220 } 221 checkState(t, c, 148, 35) 222 // can go upto 1.1*148 = 162, so should go upto 127. 223 if err := Do(context.Background(), c, 127, func() (bool, error) { return true, nil }); err != nil { 224 t.Fatal(err) 225 } 226 } 227 228 func TestRetry(t *testing.T) { 229 const ( 230 N = 1000 231 ) 232 c := ControllerWithRetry(200, 1000, retry.MaxRetries(retry.Backoff(100*time.Millisecond, time.Minute, 1.5), 5)) 233 var begin sync.WaitGroup 234 begin.Add(N) 235 err := traverse.Each(N, func(i int) error { 236 begin.Done() 237 begin.Wait() 238 randFunc := func() (CapacityStatus, error) { 239 // Out of every three requests, one will (5% of the time) report over capacity with a need to retry, 240 // and another (also 5% of the time) will report over capacity with no need to retry. 241 switch i % 3 { 242 case 0: 243 time.Sleep(time.Millisecond * time.Duration(20+rand.Intn(50))) 244 if rand.Intn(100) < 5 { // 5% of the time. 245 return OverNeedRetry, nil 246 } 247 case 1: 248 time.Sleep(time.Millisecond * time.Duration(20+rand.Intn(50))) 249 if rand.Intn(100) < 5 { // 5% of the time. 250 return OverNoRetry, nil 251 } 252 } 253 time.Sleep(time.Millisecond * time.Duration(5+rand.Intn(20))) 254 return Within, nil 255 } 256 n := rand.Intn(20) + 1 257 return Retry(context.Background(), c, n, randFunc) 258 }) 259 if err != nil { 260 t.Fatal(err) 261 } 262 }