Home | History | Annotate | Download | only in concurrent
      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 static java.lang.reflect.Modifier.isStatic;
     20 import static java.util.concurrent.TimeUnit.MICROSECONDS;
     21 import static java.util.concurrent.TimeUnit.MILLISECONDS;
     22 import static java.util.concurrent.TimeUnit.NANOSECONDS;
     23 import static java.util.concurrent.TimeUnit.SECONDS;
     24 
     25 import com.google.common.collect.ImmutableClassToInstanceMap;
     26 import com.google.common.collect.ImmutableSet;
     27 import com.google.common.collect.Lists;
     28 import com.google.common.testing.NullPointerTester;
     29 import com.google.common.testing.NullPointerTester.Visibility;
     30 import com.google.common.util.concurrent.RateLimiter.SleepingStopwatch;
     31 
     32 import junit.framework.TestCase;
     33 
     34 import org.easymock.EasyMock;
     35 import org.mockito.Mockito;
     36 
     37 import java.lang.reflect.Method;
     38 import java.util.Arrays;
     39 import java.util.List;
     40 import java.util.Random;
     41 import java.util.concurrent.TimeUnit;
     42 
     43 /**
     44  * Tests for RateLimiter.
     45  *
     46  * @author Dimitris Andreou
     47  */
     48 public class RateLimiterTest extends TestCase {
     49   private static final double EPSILON = 1e-8;
     50 
     51   private final FakeStopwatch stopwatch = new FakeStopwatch();
     52 
     53   public void testSimple() {
     54     RateLimiter limiter = RateLimiter.create(stopwatch, 5.0);
     55     limiter.acquire(); // R0.00, since it's the first request
     56     limiter.acquire(); // R0.20
     57     limiter.acquire(); // R0.20
     58     assertEvents("R0.00", "R0.20", "R0.20");
     59   }
     60 
     61   public void testImmediateTryAcquire() {
     62     RateLimiter r = RateLimiter.create(1);
     63     assertTrue("Unable to acquire initial permit", r.tryAcquire());
     64     assertFalse("Capable of acquiring secondary permit", r.tryAcquire());
     65   }
     66 
     67   public void testSimpleRateUpdate() {
     68     RateLimiter limiter = RateLimiter.create(5.0, 5, SECONDS);
     69     assertEquals(5.0, limiter.getRate());
     70     limiter.setRate(10.0);
     71     assertEquals(10.0, limiter.getRate());
     72 
     73     try {
     74       limiter.setRate(0.0);
     75       fail();
     76     } catch (IllegalArgumentException expected) {}
     77     try {
     78       limiter.setRate(-10.0);
     79       fail();
     80     } catch (IllegalArgumentException expected) {}
     81   }
     82 
     83   public void testAcquireParameterValidation() {
     84     RateLimiter limiter = RateLimiter.create(999);
     85     try {
     86       limiter.acquire(0);
     87       fail();
     88     } catch (IllegalArgumentException expected) {
     89     }
     90     try {
     91       limiter.acquire(-1);
     92       fail();
     93     } catch (IllegalArgumentException expected) {
     94     }
     95     try {
     96       limiter.tryAcquire(0);
     97       fail();
     98     } catch (IllegalArgumentException expected) {
     99     }
    100     try {
    101       limiter.tryAcquire(-1);
    102       fail();
    103     } catch (IllegalArgumentException expected) {
    104     }
    105     try {
    106       limiter.tryAcquire(0, 1, SECONDS);
    107       fail();
    108     } catch (IllegalArgumentException expected) {
    109     }
    110     try {
    111       limiter.tryAcquire(-1, 1, SECONDS);
    112       fail();
    113     } catch (IllegalArgumentException expected) {
    114     }
    115   }
    116 
    117   public void testSimpleWithWait() {
    118     RateLimiter limiter = RateLimiter.create(stopwatch, 5.0);
    119     limiter.acquire();          // R0.00
    120     stopwatch.sleepMillis(200);    // U0.20, we are ready for the next request...
    121     limiter.acquire();          // R0.00, ...which is granted immediately
    122     limiter.acquire();          // R0.20
    123     assertEvents("R0.00", "U0.20", "R0.00", "R0.20");
    124   }
    125 
    126   public void testSimpleAcquireReturnValues() {
    127     RateLimiter limiter = RateLimiter.create(stopwatch, 5.0);
    128     assertEquals(0.0, limiter.acquire(), EPSILON);  // R0.00
    129     stopwatch.sleepMillis(200);                     // U0.20, we are ready for the next request...
    130     assertEquals(0.0, limiter.acquire(), EPSILON);  // R0.00, ...which is granted immediately
    131     assertEquals(0.2, limiter.acquire(), EPSILON);  // R0.20
    132     assertEvents("R0.00", "U0.20", "R0.00", "R0.20");
    133   }
    134 
    135   public void testSimpleAcquireEarliestAvailableIsInPast() {
    136     RateLimiter limiter = RateLimiter.create(stopwatch, 5.0);
    137     assertEquals(0.0, limiter.acquire(), EPSILON);
    138     stopwatch.sleepMillis(400);
    139     assertEquals(0.0, limiter.acquire(), EPSILON);
    140     assertEquals(0.0, limiter.acquire(), EPSILON);
    141     assertEquals(0.2, limiter.acquire(), EPSILON);
    142   }
    143 
    144   public void testOneSecondBurst() {
    145     RateLimiter limiter = RateLimiter.create(stopwatch, 5.0);
    146     stopwatch.sleepMillis(1000); // max capacity reached
    147     stopwatch.sleepMillis(1000); // this makes no difference
    148     limiter.acquire(1); // R0.00, since it's the first request
    149 
    150     limiter.acquire(1); // R0.00, from capacity
    151     limiter.acquire(3); // R0.00, from capacity
    152     limiter.acquire(1); // R0.00, concluding a burst of 5 permits
    153 
    154     limiter.acquire(); // R0.20, capacity exhausted
    155     assertEvents("U1.00", "U1.00",
    156         "R0.00", "R0.00", "R0.00", "R0.00", // first request and burst
    157         "R0.20");
    158   }
    159 
    160   public void testCreateWarmupParameterValidation() {
    161     RateLimiter.create(1.0, 1, NANOSECONDS);
    162     RateLimiter.create(1.0, 0, NANOSECONDS);
    163 
    164     try {
    165       RateLimiter.create(0.0, 1, NANOSECONDS);
    166       fail();
    167     } catch (IllegalArgumentException expected) {
    168     }
    169 
    170     try {
    171       RateLimiter.create(1.0, -1, NANOSECONDS);
    172       fail();
    173     } catch (IllegalArgumentException expected) {
    174     }
    175   }
    176 
    177   public void testWarmUp() {
    178     RateLimiter limiter = RateLimiter.create(stopwatch, 2.0, 4000, MILLISECONDS);
    179     for (int i = 0; i < 8; i++) {
    180       limiter.acquire(); // #1
    181     }
    182     stopwatch.sleepMillis(500); // #2: to repay for the last acquire
    183     stopwatch.sleepMillis(4000); // #3: becomes cold again
    184     for (int i = 0; i < 8; i++) {
    185       limiter.acquire(); // // #4
    186     }
    187     stopwatch.sleepMillis(500); // #5: to repay for the last acquire
    188     stopwatch.sleepMillis(2000); // #6: didn't get cold! It would take another 2 seconds to go cold
    189     for (int i = 0; i < 8; i++) {
    190       limiter.acquire(); // #7
    191     }
    192     assertEvents(
    193         "R0.00, R1.38, R1.13, R0.88, R0.63, R0.50, R0.50, R0.50", // #1
    194         "U0.50", // #2
    195         "U4.00", // #3
    196         "R0.00, R1.38, R1.13, R0.88, R0.63, R0.50, R0.50, R0.50", // #4
    197         "U0.50", // #5
    198         "U2.00", // #6
    199         "R0.00, R0.50, R0.50, R0.50, R0.50, R0.50, R0.50, R0.50"); // #7
    200   }
    201 
    202   public void testWarmUpAndUpdate() {
    203     RateLimiter limiter = RateLimiter.create(stopwatch, 2.0, 4000, MILLISECONDS);
    204     for (int i = 0; i < 8; i++) {
    205       limiter.acquire(); // // #1
    206     }
    207     stopwatch.sleepMillis(4500); // #2: back to cold state (warmup period + repay last acquire)
    208     for (int i = 0; i < 3; i++) { // only three steps, we're somewhere in the warmup period
    209       limiter.acquire(); // #3
    210     }
    211 
    212     limiter.setRate(4.0); // double the rate!
    213     limiter.acquire(); // #4, we repay the debt of the last acquire (imposed by the old rate)
    214     for (int i = 0; i < 4; i++) {
    215       limiter.acquire(); // #5
    216     }
    217     stopwatch.sleepMillis(4250); // #6, back to cold state (warmup period + repay last acquire)
    218     for (int i = 0; i < 11; i++) {
    219       limiter.acquire(); // #7, showing off the warmup starting from totally cold
    220     }
    221 
    222     // make sure the areas (times) remain the same, while permits are different
    223     assertEvents(
    224         "R0.00, R1.38, R1.13, R0.88, R0.63, R0.50, R0.50, R0.50", // #1
    225         "U4.50", // #2
    226         "R0.00, R1.38, R1.13", // #3, after that the rate changes
    227         "R0.88", // #4, this is what the throttling would be with the old rate
    228         "R0.34, R0.28, R0.25, R0.25", // #5
    229         "U4.25", // #6
    230         "R0.00, R0.72, R0.66, R0.59, R0.53, R0.47, R0.41", // #7
    231         "R0.34, R0.28, R0.25, R0.25"); // #7 (cont.), note, this matches #5
    232   }
    233 
    234   public void testBurstyAndUpdate() {
    235     RateLimiter rateLimiter = RateLimiter.create(stopwatch, 1.0);
    236     rateLimiter.acquire(1); // no wait
    237     rateLimiter.acquire(1); // R1.00, to repay previous
    238 
    239     rateLimiter.setRate(2.0); // update the rate!
    240 
    241     rateLimiter.acquire(1); // R1.00, to repay previous (the previous was under the old rate!)
    242     rateLimiter.acquire(2); // R0.50, to repay previous (now the rate takes effect)
    243     rateLimiter.acquire(4); // R1.00, to repay previous
    244     rateLimiter.acquire(1); // R2.00, to repay previous
    245     assertEvents("R0.00", "R1.00", "R1.00", "R0.50", "R1.00", "R2.00");
    246   }
    247 
    248   public void testTryAcquire_noWaitAllowed() {
    249     RateLimiter limiter = RateLimiter.create(stopwatch, 5.0);
    250     assertTrue(limiter.tryAcquire(0, SECONDS));
    251     assertFalse(limiter.tryAcquire(0, SECONDS));
    252     assertFalse(limiter.tryAcquire(0, SECONDS));
    253     stopwatch.sleepMillis(100);
    254     assertFalse(limiter.tryAcquire(0, SECONDS));
    255   }
    256 
    257   public void testTryAcquire_someWaitAllowed() {
    258     RateLimiter limiter = RateLimiter.create(stopwatch, 5.0);
    259     assertTrue(limiter.tryAcquire(0, SECONDS));
    260     assertTrue(limiter.tryAcquire(200, MILLISECONDS));
    261     assertFalse(limiter.tryAcquire(100, MILLISECONDS));
    262     stopwatch.sleepMillis(100);
    263     assertTrue(limiter.tryAcquire(100, MILLISECONDS));
    264   }
    265 
    266   public void testTryAcquire_overflow() {
    267     RateLimiter limiter = RateLimiter.create(stopwatch, 5.0);
    268     assertTrue(limiter.tryAcquire(0, MICROSECONDS));
    269     stopwatch.sleepMillis(100);
    270     assertTrue(limiter.tryAcquire(Long.MAX_VALUE, MICROSECONDS));
    271   }
    272 
    273   public void testTryAcquire_negative() {
    274     RateLimiter limiter = RateLimiter.create(stopwatch, 5.0);
    275     assertTrue(limiter.tryAcquire(5, 0, SECONDS));
    276     stopwatch.sleepMillis(900);
    277     assertFalse(limiter.tryAcquire(1, Long.MIN_VALUE, SECONDS));
    278     stopwatch.sleepMillis(100);
    279     assertTrue(limiter.tryAcquire(1, -1, SECONDS));
    280   }
    281 
    282   public void testSimpleWeights() {
    283     RateLimiter rateLimiter = RateLimiter.create(stopwatch, 1.0);
    284     rateLimiter.acquire(1); // no wait
    285     rateLimiter.acquire(1); // R1.00, to repay previous
    286     rateLimiter.acquire(2); // R1.00, to repay previous
    287     rateLimiter.acquire(4); // R2.00, to repay previous
    288     rateLimiter.acquire(8); // R4.00, to repay previous
    289     rateLimiter.acquire(1); // R8.00, to repay previous
    290     assertEvents("R0.00", "R1.00", "R1.00", "R2.00", "R4.00", "R8.00");
    291   }
    292 
    293   public void testInfinity_Bursty() {
    294     RateLimiter limiter = RateLimiter.create(stopwatch, Double.POSITIVE_INFINITY);
    295     limiter.acquire(Integer.MAX_VALUE / 4);
    296     limiter.acquire(Integer.MAX_VALUE / 2);
    297     limiter.acquire(Integer.MAX_VALUE);
    298     assertEvents("R0.00", "R0.00", "R0.00"); // no wait, infinite rate!
    299 
    300     limiter.setRate(2.0);
    301     limiter.acquire();
    302     limiter.acquire();
    303     limiter.acquire();
    304     limiter.acquire();
    305     limiter.acquire();
    306     assertEvents(
    307         "R0.00", // First comes the saved-up burst, which defaults to a 1-second burst (2 requests).
    308         "R0.00",
    309         "R0.00", // Now comes the free request.
    310         "R0.50", // Now it's 0.5 seconds per request.
    311         "R0.50");
    312 
    313     limiter.setRate(Double.POSITIVE_INFINITY);
    314     limiter.acquire();
    315     limiter.acquire();
    316     limiter.acquire();
    317     assertEvents("R0.50", "R0.00", "R0.00"); // we repay the last request (.5sec), then back to +oo
    318   }
    319 
    320   /** https://code.google.com/p/guava-libraries/issues/detail?id=1791 */
    321   public void testInfinity_BustyTimeElapsed() {
    322     RateLimiter limiter = RateLimiter.create(stopwatch, Double.POSITIVE_INFINITY);
    323     stopwatch.instant += 1000000;
    324     limiter.setRate(2.0);
    325     for (int i = 0; i < 5; i++) {
    326       limiter.acquire();
    327     }
    328     assertEvents(
    329         "R0.00", // First comes the saved-up burst, which defaults to a 1-second burst (2 requests).
    330         "R0.00",
    331         "R0.00", // Now comes the free request.
    332         "R0.50", // Now it's 0.5 seconds per request.
    333         "R0.50");
    334   }
    335 
    336   public void testInfinity_WarmUp() {
    337     RateLimiter limiter = RateLimiter.create(
    338         stopwatch, Double.POSITIVE_INFINITY, 10, SECONDS);
    339     limiter.acquire(Integer.MAX_VALUE / 4);
    340     limiter.acquire(Integer.MAX_VALUE / 2);
    341     limiter.acquire(Integer.MAX_VALUE);
    342     assertEvents("R0.00", "R0.00", "R0.00");
    343 
    344     limiter.setRate(1.0);
    345     limiter.acquire();
    346     limiter.acquire();
    347     limiter.acquire();
    348     assertEvents("R0.00", "R1.00", "R1.00");
    349 
    350     limiter.setRate(Double.POSITIVE_INFINITY);
    351     limiter.acquire();
    352     limiter.acquire();
    353     limiter.acquire();
    354     assertEvents("R1.00", "R0.00", "R0.00");
    355   }
    356 
    357   public void testInfinity_WarmUpTimeElapsed() {
    358     RateLimiter limiter = RateLimiter.create(stopwatch, Double.POSITIVE_INFINITY, 10, SECONDS);
    359     stopwatch.instant += 1000000;
    360     limiter.setRate(1.0);
    361     for (int i = 0; i < 5; i++) {
    362       limiter.acquire();
    363     }
    364     assertEvents("R0.00", "R1.00", "R1.00", "R1.00", "R1.00");
    365   }
    366 
    367   /**
    368    * Make sure that bursts can never go above 1-second-worth-of-work for the current
    369    * rate, even when we change the rate.
    370    */
    371   public void testWeNeverGetABurstMoreThanOneSec() {
    372     RateLimiter limiter = RateLimiter.create(stopwatch, 1.0);
    373     int[] rates = { 1000, 1, 10, 1000000, 10, 1};
    374     for (int rate : rates) {
    375       int oneSecWorthOfWork = rate;
    376       stopwatch.sleepMillis(rate * 1000);
    377       limiter.setRate(rate);
    378       long burst = measureTotalTimeMillis(limiter, oneSecWorthOfWork, new Random());
    379       // we allow one second worth of work to go in a burst (i.e. take less than a second)
    380       assertTrue(burst <= 1000);
    381       long afterBurst = measureTotalTimeMillis(limiter, oneSecWorthOfWork, new Random());
    382       // but work beyond that must take at least one second
    383       assertTrue(afterBurst >= 1000);
    384     }
    385   }
    386 
    387   /**
    388    * This neat test shows that no matter what weights we use in our requests, if we push X
    389    * amount of permits in a cool state, where X = rate * timeToCoolDown, and we have
    390    * specified a timeToWarmUp() period, it will cost as the prescribed amount of time. E.g.,
    391    * calling [acquire(5), acquire(1)] takes exactly the same time as
    392    * [acquire(2), acquire(3), acquire(1)].
    393    */
    394   public void testTimeToWarmUpIsHonouredEvenWithWeights() {
    395     Random random = new Random();
    396     int maxPermits = 10;
    397     double[] qpsToTest = { 4.0, 2.0, 1.0, 0.5, 0.1 };
    398     for (int trial = 0; trial < 100; trial++) {
    399       for (double qps : qpsToTest) {
    400         // Since we know that: maxPermits = 0.5 * warmup / stableInterval;
    401         // then if maxPermits == 10, we have:
    402         // warmupSeconds = 20 / qps
    403         long warmupMillis = (long) ((2 * maxPermits / qps) * 1000.0);
    404         RateLimiter rateLimiter = RateLimiter.create(
    405             stopwatch, qps, warmupMillis, MILLISECONDS);
    406         assertEquals(warmupMillis, measureTotalTimeMillis(rateLimiter, maxPermits, random));
    407       }
    408     }
    409   }
    410 
    411   public void testNulls() {
    412     NullPointerTester tester = new NullPointerTester()
    413         .setDefault(SleepingStopwatch.class, stopwatch)
    414         .setDefault(int.class, 1);
    415     tester.testStaticMethods(RateLimiter.class, Visibility.PACKAGE);
    416     tester.testInstanceMethods(RateLimiter.create(stopwatch, 5.0), Visibility.PACKAGE);
    417   }
    418 
    419   private long measureTotalTimeMillis(RateLimiter rateLimiter, int permits, Random random) {
    420     long startTime = stopwatch.instant;
    421     while (permits > 0) {
    422       int nextPermitsToAcquire = Math.max(1, random.nextInt(permits));
    423       permits -= nextPermitsToAcquire;
    424       rateLimiter.acquire(nextPermitsToAcquire);
    425     }
    426     rateLimiter.acquire(1); // to repay for any pending debt
    427     return NANOSECONDS.toMillis(stopwatch.instant - startTime);
    428   }
    429 
    430   private void assertEvents(String... events) {
    431     assertEquals(Arrays.toString(events), stopwatch.readEventsAndClear());
    432   }
    433 
    434   /**
    435    * The stopwatch gathers events and presents them as strings.
    436    * R0.6 means a delay of 0.6 seconds caused by the (R)ateLimiter
    437    * U1.0 means the (U)ser caused the stopwatch to sleep for a second.
    438    */
    439   static class FakeStopwatch extends SleepingStopwatch {
    440     long instant = 0L;
    441     final List<String> events = Lists.newArrayList();
    442 
    443     @Override
    444     public long readMicros() {
    445       return NANOSECONDS.toMicros(instant);
    446     }
    447 
    448     void sleepMillis(int millis) {
    449       sleepMicros("U", MILLISECONDS.toMicros(millis));
    450     }
    451 
    452     void sleepMicros(String caption, long micros) {
    453       instant += MICROSECONDS.toNanos(micros);
    454       events.add(caption + String.format("%3.2f", (micros / 1000000.0)));
    455     }
    456 
    457     @Override
    458     void sleepMicrosUninterruptibly(long micros) {
    459       sleepMicros("R", micros);
    460     }
    461 
    462     String readEventsAndClear() {
    463       try {
    464         return events.toString();
    465       } finally {
    466         events.clear();
    467       }
    468     }
    469 
    470     @Override
    471     public String toString() {
    472       return events.toString();
    473     }
    474   }
    475 
    476   public void testMocking() throws Exception {
    477     RateLimiter mockito = Mockito.mock(RateLimiter.class);
    478     RateLimiter easyMock = EasyMock.createNiceMock(RateLimiter.class);
    479     EasyMock.replay(easyMock);
    480     for (Method method : RateLimiter.class.getMethods()) {
    481       if (!isStatic(method.getModifiers())
    482           && !NOT_WORKING_ON_MOCKS.contains(method.getName())
    483           && !method.getDeclaringClass().equals(Object.class)) {
    484         method.invoke(mockito, arbitraryParameters(method));
    485         method.invoke(easyMock, arbitraryParameters(method));
    486       }
    487     }
    488   }
    489 
    490   private static Object[] arbitraryParameters(Method method) {
    491     Class<?>[] parameterTypes = method.getParameterTypes();
    492     Object[] params = new Object[parameterTypes.length];
    493     for (int i = 0; i < parameterTypes.length; i++) {
    494       params[i] = PARAMETER_VALUES.get(parameterTypes[i]);
    495     }
    496     return params;
    497   }
    498 
    499   private static final ImmutableSet<String> NOT_WORKING_ON_MOCKS =
    500       ImmutableSet.of("latestPermitAgeSec", "setRate");
    501 
    502   // We would use ArbitraryInstances, but it returns 0, invalid for many RateLimiter methods.
    503   private static final ImmutableClassToInstanceMap<Object> PARAMETER_VALUES =
    504       ImmutableClassToInstanceMap.builder()
    505           .put(int.class, 1)
    506           .put(long.class, 1L)
    507           .put(double.class, 1.0)
    508           .put(TimeUnit.class, SECONDS)
    509           .build();
    510 }
    511