• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2012 The Guava Authors
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.google.common.util.concurrent;
18 
19 import com.google.common.collect.Lists;
20 import com.google.common.testing.NullPointerTester;
21 import com.google.common.testing.NullPointerTester.Visibility;
22 
23 import junit.framework.TestCase;
24 
25 import java.util.Arrays;
26 import java.util.List;
27 import java.util.Random;
28 import java.util.concurrent.TimeUnit;
29 
30 /**
31  * Tests for RateLimiter.
32  *
33  * @author Dimitris Andreou
34  */
35 public class RateLimiterTest extends TestCase {
36   private static final double EPSILON = 1e-8;
37 
38   /**
39    * The ticker gathers events and presents them as strings.
40    * R0.6 means a delay of 0.6 seconds caused by the (R)ateLimiter
41    * U1.0 means the (U)ser caused the ticker to sleep for a second.
42    */
43   private final FakeTicker ticker = new FakeTicker();
44 
testSimple()45   public void testSimple() {
46     RateLimiter limiter = RateLimiter.create(ticker, 5.0);
47     limiter.acquire(); // R0.00, since it's the first request
48     limiter.acquire(); // R0.20
49     limiter.acquire(); // R0.20
50     assertEvents("R0.00", "R0.20", "R0.20");
51   }
52 
testImmediateTryAcquire()53   public void testImmediateTryAcquire() {
54     RateLimiter r = RateLimiter.create(1);
55     assertTrue("Unable to acquire initial permit", r.tryAcquire());
56     assertFalse("Capable of acquiring secondary permit", r.tryAcquire());
57   }
58 
testSimpleRateUpdate()59   public void testSimpleRateUpdate() {
60     RateLimiter limiter = RateLimiter.create(5.0, 5, TimeUnit.SECONDS);
61     assertEquals(5.0, limiter.getRate());
62     limiter.setRate(10.0);
63     assertEquals(10.0, limiter.getRate());
64 
65     try {
66       limiter.setRate(0.0);
67       fail();
68     } catch (IllegalArgumentException ok) {}
69     try {
70       limiter.setRate(-10.0);
71       fail();
72     } catch (IllegalArgumentException ok) {}
73   }
74 
testSimpleWithWait()75   public void testSimpleWithWait() {
76     RateLimiter limiter = RateLimiter.create(ticker, 5.0);
77     limiter.acquire();          // R0.00
78     ticker.sleepMillis(200);    // U0.20, we are ready for the next request...
79     limiter.acquire();          // R0.00, ...which is granted immediately
80     limiter.acquire();          // R0.20
81     assertEvents("R0.00", "U0.20", "R0.00", "R0.20");
82   }
83 
testSimpleAcquireReturnValues()84   public void testSimpleAcquireReturnValues() {
85     RateLimiter limiter = RateLimiter.create(ticker, 5.0);
86     assertEquals(0.0, limiter.acquire(), EPSILON);  // R0.00
87     ticker.sleepMillis(200);                        // U0.20, we are ready for the next request...
88     assertEquals(0.0, limiter.acquire(), EPSILON);  // R0.00, ...which is granted immediately
89     assertEquals(0.2, limiter.acquire(), EPSILON);  // R0.20
90     assertEvents("R0.00", "U0.20", "R0.00", "R0.20");
91   }
92 
testOneSecondBurst()93   public void testOneSecondBurst() {
94     RateLimiter limiter = RateLimiter.create(ticker, 5.0);
95     ticker.sleepMillis(1000); // max capacity reached
96     ticker.sleepMillis(1000); // this makes no difference
97     limiter.acquire(1); // R0.00, since it's the first request
98 
99     limiter.acquire(1); // R0.00, from capacity
100     limiter.acquire(3); // R0.00, from capacity
101     limiter.acquire(1); // R0.00, concluding a burst of 5 permits
102 
103     limiter.acquire(); // R0.20, capacity exhausted
104     assertEvents("U1.00", "U1.00",
105         "R0.00", "R0.00", "R0.00", "R0.00", // first request and burst
106         "R0.20");
107   }
108 
testWarmUp()109   public void testWarmUp() {
110     RateLimiter limiter = RateLimiter.create(ticker, 2.0, 4000, TimeUnit.MILLISECONDS);
111     for (int i = 0; i < 8; i++) {
112       limiter.acquire(); // #1
113     }
114     ticker.sleepMillis(500); // #2: to repay for the last acquire
115     ticker.sleepMillis(4000); // #3: becomes cold again
116     for (int i = 0; i < 8; i++) {
117       limiter.acquire(); // // #4
118     }
119     ticker.sleepMillis(500); // #5: to repay for the last acquire
120     ticker.sleepMillis(2000); // #6: didn't get cold! It would take another 2 seconds to go cold
121     for (int i = 0; i < 8; i++) {
122       limiter.acquire(); // #7
123     }
124     assertEvents(
125         "R0.00, R1.38, R1.13, R0.88, R0.63, R0.50, R0.50, R0.50", // #1
126         "U0.50", // #2
127         "U4.00", // #3
128         "R0.00, R1.38, R1.13, R0.88, R0.63, R0.50, R0.50, R0.50", // #4
129         "U0.50", // #5
130         "U2.00", // #6
131         "R0.00, R0.50, R0.50, R0.50, R0.50, R0.50, R0.50, R0.50"); // #7
132   }
133 
testWarmUpAndUpdate()134   public void testWarmUpAndUpdate() {
135     RateLimiter limiter = RateLimiter.create(ticker, 2.0, 4000, TimeUnit.MILLISECONDS);
136     for (int i = 0; i < 8; i++) {
137       limiter.acquire(); // // #1
138     }
139     ticker.sleepMillis(4500); // #2: back to cold state (warmup period + repay last acquire)
140     for (int i = 0; i < 3; i++) { // only three steps, we're somewhere in the warmup period
141       limiter.acquire(); // #3
142     }
143 
144     limiter.setRate(4.0); // double the rate!
145     limiter.acquire(); // #4, we repay the debt of the last acquire (imposed by the old rate)
146     for (int i = 0; i < 4; i++) {
147       limiter.acquire(); // #5
148     }
149     ticker.sleepMillis(4250); // #6, back to cold state (warmup period + repay last acquire)
150     for (int i = 0; i < 11; i++) {
151       limiter.acquire(); // #7, showing off the warmup starting from totally cold
152     }
153 
154     // make sure the areas (times) remain the same, while permits are different
155     assertEvents(
156         "R0.00, R1.38, R1.13, R0.88, R0.63, R0.50, R0.50, R0.50", // #1
157         "U4.50", // #2
158         "R0.00, R1.38, R1.13", // #3, after that the rate changes
159         "R0.88", // #4, this is what the throttling would be with the old rate
160         "R0.34, R0.28, R0.25, R0.25", // #5
161         "U4.25", // #6
162         "R0.00, R0.72, R0.66, R0.59, R0.53, R0.47, R0.41", // #7
163         "R0.34, R0.28, R0.25, R0.25"); // #7 (cont.), note, this matches #5
164   }
165 
testBursty()166   public void testBursty() {
167     RateLimiter limiter = RateLimiter.createWithCapacity(ticker, 1.0, 10, TimeUnit.SECONDS);
168     ticker.sleepMillis(10000); // reach full capacity
169     limiter.acquire(11); // all these are served in a burst (10 + 1 by borrowing from the future)
170     limiter.acquire(1); // out of capacity, we have to wait
171     limiter.acquire(1); // and wait
172     ticker.sleepMillis(3000); // fill up 3 permits
173     limiter.acquire(5); // we had 3 ready, thus we borrow 2 permits
174     limiter.acquire(1); // this acquire() will also repay for the previous acquire()
175     assertEvents(
176         "U10.00",
177         "R0.00", // 10 permits grabbed
178         "R1.00", "R1.00", // 1 and 1
179         "U3.00", "R0.00", // 5 grabbed
180         "R3.00" // 1 grabbed
181         );
182   }
183 
testBurstyAndUpdate()184   public void testBurstyAndUpdate() {
185     RateLimiter rateLimiter = RateLimiter.create(ticker, 1.0);
186     rateLimiter.acquire(1); // no wait
187     rateLimiter.acquire(1); // R1.00, to repay previous
188 
189     rateLimiter.setRate(2.0); // update the rate!
190 
191     rateLimiter.acquire(1); // R1.00, to repay previous (the previous was under the old rate!)
192     rateLimiter.acquire(2); // R0.50, to repay previous (now the rate takes effect)
193     rateLimiter.acquire(4); // R1.00, to repay previous
194     rateLimiter.acquire(1); // R2.00, to repay previous
195     assertEvents("R0.00", "R1.00", "R1.00", "R0.50", "R1.00", "R2.00");
196   }
197 
testTimeWrapping()198   public void testTimeWrapping() {
199     ticker.instant = Long.MAX_VALUE - TimeUnit.SECONDS.toNanos(1); // 1 second before max value
200     RateLimiter limiter = RateLimiter.create(ticker, 1.0);
201     for (int i = 0; i < 4; i++) {
202       limiter.acquire();
203     }
204     // Without protection from overflow, the last wait value would have been huge,
205     // because "now" would have wrapped into a value near MIN_VALUE, and the limiter would think
206     // that the next request should be admitted far into the future
207     assertEvents("R0.00", "R1.00", "R1.00", "R1.00");
208   }
209 
testTryGate()210   public void testTryGate() {
211     RateLimiter limiter = RateLimiter.create(ticker, 5.0);
212     assertTrue(limiter.tryAcquire(0, TimeUnit.SECONDS));
213     assertFalse(limiter.tryAcquire(0, TimeUnit.SECONDS));
214     assertFalse(limiter.tryAcquire(0, TimeUnit.SECONDS));
215     ticker.sleepMillis(100);
216     assertFalse(limiter.tryAcquire(0, TimeUnit.SECONDS));
217   }
218 
testSimpleWeights()219   public void testSimpleWeights() {
220     RateLimiter rateLimiter = RateLimiter.create(ticker, 1.0);
221     rateLimiter.acquire(1); // no wait
222     rateLimiter.acquire(1); // R1.00, to repay previous
223     rateLimiter.acquire(2); // R1.00, to repay previous
224     rateLimiter.acquire(4); // R2.00, to repay previous
225     rateLimiter.acquire(8); // R4.00, to repay previous
226     rateLimiter.acquire(1); // R8.00, to repay previous
227     assertEvents("R0.00", "R1.00", "R1.00", "R2.00", "R4.00", "R8.00");
228   }
229 
testInfinity_Bursty()230   public void testInfinity_Bursty() {
231     RateLimiter limiter = RateLimiter.create(ticker, Double.POSITIVE_INFINITY);
232     limiter.acquire(Integer.MAX_VALUE / 4);
233     limiter.acquire(Integer.MAX_VALUE / 2);
234     limiter.acquire(Integer.MAX_VALUE);
235     assertEvents("R0.00", "R0.00", "R0.00"); // no wait, infinite rate!
236 
237     limiter.setRate(1.0);
238     limiter.acquire();
239     limiter.acquire();
240     limiter.acquire();
241     assertEvents("R0.00", "R1.00", "R1.00"); // we repay the last request (but that had no cost)
242     // and then we go to 1 second per request mode
243 
244     limiter.setRate(Double.POSITIVE_INFINITY);
245     limiter.acquire();
246     limiter.acquire();
247     limiter.acquire();
248     assertEvents("R1.00", "R0.00", "R0.00"); // we repay the last request (1sec), then back to +oo
249   }
250 
testInfinity_WarmUp()251   public void testInfinity_WarmUp() {
252     RateLimiter limiter = RateLimiter.create(
253         ticker, Double.POSITIVE_INFINITY, 10, TimeUnit.SECONDS);
254     limiter.acquire(Integer.MAX_VALUE / 4);
255     limiter.acquire(Integer.MAX_VALUE / 2);
256     limiter.acquire(Integer.MAX_VALUE);
257     assertEvents("R0.00", "R0.00", "R0.00");
258 
259     limiter.setRate(1.0);
260     limiter.acquire();
261     limiter.acquire();
262     limiter.acquire();
263     assertEvents("R0.00", "R1.00", "R1.00");
264 
265     limiter.setRate(Double.POSITIVE_INFINITY);
266     limiter.acquire();
267     limiter.acquire();
268     limiter.acquire();
269     assertEvents("R1.00", "R0.00", "R0.00");
270   }
271 
272   /**
273    * Make sure that bursts can never go above 1-second-worth-of-work for the current
274    * rate, even when we change the rate.
275    */
testWeNeverGetABurstMoreThanOneSec()276   public void testWeNeverGetABurstMoreThanOneSec() {
277     RateLimiter limiter = RateLimiter.create(ticker, 1.0);
278     int[] rates = { 1000, 1, 10, 1000000, 10, 1};
279     for (int rate : rates) {
280       int oneSecWorthOfWork = rate;
281       ticker.sleepMillis(rate * 1000);
282       limiter.setRate(rate);
283       long burst = measureTotalTimeMillis(limiter, oneSecWorthOfWork, new Random());
284       // we allow one second worth of work to go in a burst (i.e. take less than a second)
285       assertTrue(burst <= 1000);
286       long afterBurst = measureTotalTimeMillis(limiter, oneSecWorthOfWork, new Random());
287       // but work beyond that must take at least one second
288       assertTrue(afterBurst >= 1000);
289     }
290   }
291 
292   /**
293    * This neat test shows that no matter what weights we use in our requests, if we push X
294    * amount of permits in a cool state, where X = rate * timeToCoolDown, and we have
295    * specified a timeToWarmUp() period, it will cost as the prescribed amount of time. E.g.,
296    * calling [acquire(5), acquire(1)] takes exactly the same time as
297    * [acquire(2), acquire(3), acquire(1)].
298    */
testTimeToWarmUpIsHonouredEvenWithWeights()299   public void testTimeToWarmUpIsHonouredEvenWithWeights() {
300     Random random = new Random();
301     int maxPermits = 10;
302     double[] qpsToTest = { 4.0, 2.0, 1.0, 0.5, 0.1 };
303     for (int trial = 0; trial < 100; trial++) {
304       for (double qps : qpsToTest) {
305         // Since we know that: maxPermits = 0.5 * warmup / stableInterval;
306         // then if maxPermits == 10, we have:
307         // warmupSeconds = 20 / qps
308         long warmupMillis = (long) ((2 * maxPermits / qps) * 1000.0);
309         RateLimiter rateLimiter = RateLimiter.create(
310             ticker, qps, warmupMillis, TimeUnit.MILLISECONDS);
311         assertEquals(warmupMillis, measureTotalTimeMillis(rateLimiter, maxPermits, random));
312       }
313     }
314   }
315 
testNulls()316   public void testNulls() {
317     NullPointerTester tester = new NullPointerTester()
318         .setDefault(RateLimiter.SleepingTicker.class, ticker);
319     tester.testStaticMethods(RateLimiter.class, Visibility.PACKAGE);
320     tester.testInstanceMethods(RateLimiter.create(ticker, 5.0), Visibility.PACKAGE);
321   }
322 
measureTotalTimeMillis(RateLimiter rateLimiter, int permits, Random random)323   private long measureTotalTimeMillis(RateLimiter rateLimiter, int permits, Random random) {
324     long startTime = ticker.instant;
325     while (permits > 0) {
326       int nextPermitsToAcquire = Math.max(1, random.nextInt(permits));
327       permits -= nextPermitsToAcquire;
328       rateLimiter.acquire(nextPermitsToAcquire);
329     }
330     rateLimiter.acquire(1); // to repay for any pending debt
331     return TimeUnit.NANOSECONDS.toMillis(ticker.instant - startTime);
332   }
333 
assertEvents(String... events)334   private void assertEvents(String... events) {
335     assertEquals(Arrays.toString(events), ticker.readEventsAndClear());
336   }
337 
338   private static class FakeTicker extends RateLimiter.SleepingTicker {
339     long instant = 0L;
340     final List<String> events = Lists.newArrayList();
341 
342     @Override
read()343     public long read() {
344       return instant;
345     }
346 
sleepMillis(int millis)347     void sleepMillis(int millis) {
348       sleepMicros("U", TimeUnit.MILLISECONDS.toMicros(millis));
349     }
350 
sleepMicros(String caption, long micros)351     void sleepMicros(String caption, long micros) {
352       instant += TimeUnit.MICROSECONDS.toNanos(micros);
353       events.add(caption + String.format("%3.2f", (micros / 1000000.0)));
354     }
355 
356     @Override
sleepMicrosUninterruptibly(long micros)357     void sleepMicrosUninterruptibly(long micros) {
358       sleepMicros("R", micros);
359     }
360 
readEventsAndClear()361     String readEventsAndClear() {
362       try {
363         return events.toString();
364       } finally {
365         events.clear();
366       }
367     }
368 
369     @Override
toString()370     public String toString() {
371       return events.toString();
372     }
373   }
374 }
375