View Javadoc

1   /**
2    * Licensed to the Apache Software Foundation (ASF) under one or more contributor license
3    * agreements. See the NOTICE file distributed with this work for additional information regarding
4    * copyright ownership. The ASF licenses this file to you under the Apache License, Version 2.0 (the
5    * "License"); you may not use this file except in compliance with the License. You may obtain a
6    * copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable
7    * law or agreed to in writing, software distributed under the License is distributed on an "AS IS"
8    * BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License
9    * for the specific language governing permissions and limitations under the License.
10   */
11  
12  package org.apache.hadoop.hbase.quotas;
13  
14  import static org.junit.Assert.assertEquals;
15  import static org.junit.Assert.assertFalse;
16  import static org.junit.Assert.assertTrue;
17  
18  import java.util.concurrent.TimeUnit;
19  import org.apache.hadoop.hbase.testclassification.SmallTests;
20  import org.apache.hadoop.hbase.util.EnvironmentEdge;
21  import org.apache.hadoop.hbase.util.EnvironmentEdgeManager;
22  import org.apache.hadoop.hbase.util.ManualEnvironmentEdge;
23  import org.junit.Test;
24  import org.junit.experimental.categories.Category;
25  
26  /**
27   * Verify the behaviour of the Rate Limiter.
28   */
29  @Category({ SmallTests.class })
30  public class TestRateLimiter {
31    @Test
32    public void testWaitIntervalTimeUnitSeconds() {
33      testWaitInterval(TimeUnit.SECONDS, 10, 100);
34    }
35  
36    @Test
37    public void testWaitIntervalTimeUnitMinutes() {
38      testWaitInterval(TimeUnit.MINUTES, 10, 6000);
39    }
40  
41    @Test
42    public void testWaitIntervalTimeUnitHours() {
43      testWaitInterval(TimeUnit.HOURS, 10, 360000);
44    }
45  
46    @Test
47    public void testWaitIntervalTimeUnitDays() {
48      testWaitInterval(TimeUnit.DAYS, 10, 8640000);
49    }
50  
51    private void testWaitInterval(final TimeUnit timeUnit, final long limit,
52        final long expectedWaitInterval) {
53      RateLimiter limiter = new AverageIntervalRateLimiter();
54      limiter.set(limit, timeUnit);
55  
56      long nowTs = 0;
57      // consume all the available resources, one request at the time.
58      // the wait interval should be 0
59      for (int i = 0; i < (limit - 1); ++i) {
60        assertTrue(limiter.canExecute());
61        limiter.consume();
62        long waitInterval = limiter.waitInterval();
63        assertEquals(0, waitInterval);
64      }
65  
66      for (int i = 0; i < (limit * 4); ++i) {
67        // There is one resource available, so we should be able to
68        // consume it without waiting.
69        limiter.setNextRefillTime(limiter.getNextRefillTime() - nowTs);
70        assertTrue(limiter.canExecute());
71        assertEquals(0, limiter.waitInterval());
72        limiter.consume();
73        // No more resources are available, we should wait for at least an interval.
74        long waitInterval = limiter.waitInterval();
75        assertEquals(expectedWaitInterval, waitInterval);
76  
77        // set the nowTs to be the exact time when resources should be available again.
78        nowTs = waitInterval;
79  
80        // artificially go into the past to prove that when too early we should fail.
81        long temp = nowTs + 500;
82        limiter.setNextRefillTime(limiter.getNextRefillTime() + temp);
83        assertFalse(limiter.canExecute());
84        //Roll back the nextRefillTime set to continue further testing
85        limiter.setNextRefillTime(limiter.getNextRefillTime() - temp);
86      }
87    }
88  
89    @Test
90    public void testOverconsumptionAverageIntervalRefillStrategy() {
91      RateLimiter limiter = new AverageIntervalRateLimiter();
92      limiter.set(10, TimeUnit.SECONDS);
93  
94      // 10 resources are available, but we need to consume 20 resources
95      // Verify that we have to wait at least 1.1sec to have 1 resource available
96      assertTrue(limiter.canExecute());
97      limiter.consume(20);
98      // To consume 1 resource wait for 100ms
99      assertEquals(100, limiter.waitInterval(1));
100     // To consume 10 resource wait for 1000ms
101     assertEquals(1000, limiter.waitInterval(10));
102 
103     limiter.setNextRefillTime(limiter.getNextRefillTime() - 900);
104     // Verify that after 1sec the 1 resource is available
105     assertTrue(limiter.canExecute(1));
106     limiter.setNextRefillTime(limiter.getNextRefillTime() - 100);
107     // Verify that after 1sec the 10 resource is available
108     assertTrue(limiter.canExecute());
109     assertEquals(0, limiter.waitInterval());
110   }
111 
112   @Test
113   public void testOverconsumptionFixedIntervalRefillStrategy() throws InterruptedException {
114     RateLimiter limiter = new FixedIntervalRateLimiter();
115     limiter.set(10, TimeUnit.SECONDS);
116 
117     // fix the current time in order to get the precise value of interval
118     EnvironmentEdge edge = new EnvironmentEdge() {
119       private final long ts = System.currentTimeMillis();
120       @Override
121       public long currentTime() {
122         return ts;
123       }
124     };
125     EnvironmentEdgeManager.injectEdge(edge);
126     // 10 resources are available, but we need to consume 20 resources
127     // Verify that we have to wait at least 1.1sec to have 1 resource available
128     assertTrue(limiter.canExecute());
129     limiter.consume(20);
130     // To consume 1 resource also wait for 1000ms
131     assertEquals(1000, limiter.waitInterval(1));
132     // To consume 10 resource wait for 100ms
133     assertEquals(1000, limiter.waitInterval(10));
134     EnvironmentEdgeManager.reset();
135 
136     limiter.setNextRefillTime(limiter.getNextRefillTime() - 900);
137     // Verify that after 1sec also no resource should be available
138     assertFalse(limiter.canExecute(1));
139     limiter.setNextRefillTime(limiter.getNextRefillTime() - 100);
140 
141     // Verify that after 1sec the 10 resource is available
142     assertTrue(limiter.canExecute());
143     assertEquals(0, limiter.waitInterval());
144   }
145 
146   @Test
147   public void testFixedIntervalResourceAvailability() throws Exception {
148     RateLimiter limiter = new FixedIntervalRateLimiter();
149     limiter.set(10, TimeUnit.SECONDS);
150 
151     assertTrue(limiter.canExecute(10));
152     limiter.consume(3);
153     assertEquals(7, limiter.getAvailable());
154     assertFalse(limiter.canExecute(10));
155     limiter.setNextRefillTime(limiter.getNextRefillTime() - 1000);
156     assertTrue(limiter.canExecute(10));
157     assertEquals(10, limiter.getAvailable());
158   }
159 
160   @Test
161   public void testLimiterBySmallerRate() throws InterruptedException {
162     // set limiter is 10 resources per seconds
163     RateLimiter limiter = new FixedIntervalRateLimiter();
164     limiter.set(10, TimeUnit.SECONDS);
165 
166     int count = 0; // control the test count
167     while ((count++) < 10) {
168       // test will get 3 resources per 0.5 sec. so it will get 6 resources per sec.
169       limiter.setNextRefillTime(limiter.getNextRefillTime() - 500);
170       for (int i = 0; i < 3; i++) {
171         // 6 resources/sec < limit, so limiter.canExecute(nowTs, lastTs) should be true
172         assertEquals(true, limiter.canExecute());
173         limiter.consume();
174       }
175     }
176   }
177 
178   @Test
179   public void testCanExecuteOfAverageIntervalRateLimiter() throws InterruptedException {
180     RateLimiter limiter = new AverageIntervalRateLimiter();
181     // when set limit is 100 per sec, this AverageIntervalRateLimiter will support at max 200 per sec
182     limiter.set(100, TimeUnit.SECONDS);
183     limiter.setNextRefillTime(EnvironmentEdgeManager.currentTime());
184     assertEquals(50, testCanExecuteByRate(limiter, 50));
185 
186     // refill the avail to limit
187     limiter.set(100, TimeUnit.SECONDS);
188     limiter.setNextRefillTime(EnvironmentEdgeManager.currentTime());
189     assertEquals(100, testCanExecuteByRate(limiter, 100));
190 
191     // refill the avail to limit
192     limiter.set(100, TimeUnit.SECONDS);
193     limiter.setNextRefillTime(EnvironmentEdgeManager.currentTime());
194     assertEquals(200, testCanExecuteByRate(limiter, 200));
195 
196     // refill the avail to limit
197     limiter.set(100, TimeUnit.SECONDS);
198     limiter.setNextRefillTime(EnvironmentEdgeManager.currentTime());
199     assertEquals(200, testCanExecuteByRate(limiter, 500));
200   }
201 
202   @Test
203   public void testCanExecuteOfFixedIntervalRateLimiter() throws InterruptedException {
204     RateLimiter limiter = new FixedIntervalRateLimiter();
205     // when set limit is 100 per sec, this FixedIntervalRateLimiter will support at max 100 per sec
206     limiter.set(100, TimeUnit.SECONDS);
207     limiter.setNextRefillTime(EnvironmentEdgeManager.currentTime());
208     assertEquals(50, testCanExecuteByRate(limiter, 50));
209 
210     // refill the avail to limit
211     limiter.set(100, TimeUnit.SECONDS);
212     limiter.setNextRefillTime(EnvironmentEdgeManager.currentTime());
213     assertEquals(100, testCanExecuteByRate(limiter, 100));
214 
215     // refill the avail to limit
216     limiter.set(100, TimeUnit.SECONDS);
217     limiter.setNextRefillTime(EnvironmentEdgeManager.currentTime());
218     assertEquals(100, testCanExecuteByRate(limiter, 200));
219   }
220 
221   public int testCanExecuteByRate(RateLimiter limiter, int rate) {
222     int request = 0;
223     int count = 0;
224     while ((request++) < rate) {
225       limiter.setNextRefillTime(limiter.getNextRefillTime() - limiter.getTimeUnitInMillis() / rate);
226       if (limiter.canExecute()) {
227         count++;
228         limiter.consume();
229       }
230     }
231     return count;
232   }
233 
234   @Test
235   public void testRefillOfAverageIntervalRateLimiter() throws InterruptedException {
236     RateLimiter limiter = new AverageIntervalRateLimiter();
237     limiter.set(60, TimeUnit.SECONDS);
238     assertEquals(60, limiter.getAvailable());
239     // first refill, will return the number same with limit
240     assertEquals(60, limiter.refill(limiter.getLimit()));
241 
242     limiter.consume(30);
243 
244     // after 0.2 sec, refill should return 12
245     limiter.setNextRefillTime(limiter.getNextRefillTime() - 200);
246     assertEquals(12, limiter.refill(limiter.getLimit()));
247 
248     // after 0.5 sec, refill should return 30
249     limiter.setNextRefillTime(limiter.getNextRefillTime() - 500);
250     assertEquals(30, limiter.refill(limiter.getLimit()));
251 
252     // after 1 sec, refill should return 60
253     limiter.setNextRefillTime(limiter.getNextRefillTime() - 1000);
254     assertEquals(60, limiter.refill(limiter.getLimit()));
255 
256     // after more than 1 sec, refill should return at max 60
257     limiter.setNextRefillTime(limiter.getNextRefillTime() - 3000);
258     assertEquals(60, limiter.refill(limiter.getLimit()));
259     limiter.setNextRefillTime(limiter.getNextRefillTime() - 5000);
260     assertEquals(60, limiter.refill(limiter.getLimit()));
261   }
262 
263   @Test
264   public void testRefillOfFixedIntervalRateLimiter() throws InterruptedException {
265     RateLimiter limiter = new FixedIntervalRateLimiter();
266     limiter.set(60, TimeUnit.SECONDS);
267     assertEquals(60, limiter.getAvailable());
268     // first refill, will return the number same with limit
269     assertEquals(60, limiter.refill(limiter.getLimit()));
270 
271     limiter.consume(30);
272 
273     // after 0.2 sec, refill should return 0
274     limiter.setNextRefillTime(limiter.getNextRefillTime() - 200);
275     assertEquals(0, limiter.refill(limiter.getLimit()));
276 
277     // after 0.5 sec, refill should return 0
278     limiter.setNextRefillTime(limiter.getNextRefillTime() - 500);
279     assertEquals(0, limiter.refill(limiter.getLimit()));
280 
281     // after 1 sec, refill should return 60
282     limiter.setNextRefillTime(limiter.getNextRefillTime() - 1000);
283     assertEquals(60, limiter.refill(limiter.getLimit()));
284 
285     // after more than 1 sec, refill should return at max 60
286     limiter.setNextRefillTime(limiter.getNextRefillTime() - 3000);
287     assertEquals(60, limiter.refill(limiter.getLimit()));
288     limiter.setNextRefillTime(limiter.getNextRefillTime() - 5000);
289     assertEquals(60, limiter.refill(limiter.getLimit()));
290   }
291 
292   @Test
293   public void testUnconfiguredLimiters() throws InterruptedException {
294 
295     ManualEnvironmentEdge testEdge = new ManualEnvironmentEdge();
296     EnvironmentEdgeManager.injectEdge(testEdge);
297     long limit = Long.MAX_VALUE;
298 
299     // For unconfigured limiters, it is supposed to use as much as possible
300     RateLimiter avgLimiter = new AverageIntervalRateLimiter();
301     RateLimiter fixLimiter = new FixedIntervalRateLimiter();
302 
303     assertEquals(limit, avgLimiter.getAvailable());
304     assertEquals(limit, fixLimiter.getAvailable());
305 
306     assertTrue(avgLimiter.canExecute(limit));
307     avgLimiter.consume(limit);
308 
309     assertTrue(fixLimiter.canExecute(limit));
310     fixLimiter.consume(limit);
311 
312     // Make sure that available is Long.MAX_VALUE
313     assertTrue(limit == avgLimiter.getAvailable());
314     assertTrue(limit == fixLimiter.getAvailable());
315 
316     // after 100 millseconds, it should be able to execute limit as well
317     testEdge.incValue(100);
318 
319     assertTrue(avgLimiter.canExecute(limit));
320     avgLimiter.consume(limit);
321 
322     assertTrue(fixLimiter.canExecute(limit));
323     fixLimiter.consume(limit);
324 
325     // Make sure that available is Long.MAX_VALUE
326     assertTrue(limit == avgLimiter.getAvailable());
327     assertTrue(limit == fixLimiter.getAvailable());
328 
329     EnvironmentEdgeManager.reset();
330   }
331 
332   @Test
333   public void testExtremeLimiters() throws InterruptedException {
334 
335     ManualEnvironmentEdge testEdge = new ManualEnvironmentEdge();
336     EnvironmentEdgeManager.injectEdge(testEdge);
337     long limit = Long.MAX_VALUE - 1;
338 
339     RateLimiter avgLimiter = new AverageIntervalRateLimiter();
340     avgLimiter.set(limit, TimeUnit.SECONDS);
341     RateLimiter fixLimiter = new FixedIntervalRateLimiter();
342     fixLimiter.set(limit, TimeUnit.SECONDS);
343 
344     assertEquals(limit, avgLimiter.getAvailable());
345     assertEquals(limit, fixLimiter.getAvailable());
346 
347     assertTrue(avgLimiter.canExecute(limit / 2));
348     avgLimiter.consume(limit / 2);
349 
350     assertTrue(fixLimiter.canExecute(limit / 2));
351     fixLimiter.consume(limit / 2);
352 
353     // Make sure that available is whatever left
354     assertTrue((limit - (limit / 2)) == avgLimiter.getAvailable());
355     assertTrue((limit - (limit / 2)) == fixLimiter.getAvailable());
356 
357     // after 100 millseconds, both should not be able to execute the limit
358     testEdge.incValue(100);
359 
360     assertFalse(avgLimiter.canExecute(limit));
361     assertFalse(fixLimiter.canExecute(limit));
362 
363     // after 500 millseconds, average interval limiter should be able to execute the limit
364     testEdge.incValue(500);
365     assertTrue(avgLimiter.canExecute(limit));
366     assertFalse(fixLimiter.canExecute(limit));
367 
368     // Make sure that available is correct
369     assertTrue(limit == avgLimiter.getAvailable());
370     assertTrue((limit - (limit / 2)) == fixLimiter.getAvailable());
371 
372     // after 500 millseconds, both should be able to execute
373     testEdge.incValue(500);
374     assertTrue(avgLimiter.canExecute(limit));
375     assertTrue(fixLimiter.canExecute(limit));
376 
377     // Make sure that available is Long.MAX_VALUE
378     assertTrue(limit == avgLimiter.getAvailable());
379     assertTrue(limit == fixLimiter.getAvailable());
380 
381     EnvironmentEdgeManager.reset();
382   }
383 
384   /*
385    * This test case is tricky. Basically, it simulates the following events:
386    *           Thread-1                             Thread-2
387    * t0:  canExecute(100) and consume(100)
388    * t1:                                         canExecute(100), avail may be increased by 80
389    * t2:  consume(-80) as actual size is 20
390    * It will check if consume(-80) can handle overflow correctly.
391    */
392   @Test
393   public void testLimiterCompensationOverflow() throws InterruptedException {
394 
395     long limit = Long.MAX_VALUE - 1;
396     long guessNumber = 100;
397 
398     // For unconfigured limiters, it is supposed to use as much as possible
399     RateLimiter avgLimiter = new AverageIntervalRateLimiter();
400     avgLimiter.set(limit, TimeUnit.SECONDS);
401 
402     assertEquals(limit, avgLimiter.getAvailable());
403 
404     // The initial guess is that 100 bytes.
405     assertTrue(avgLimiter.canExecute(guessNumber));
406     avgLimiter.consume(guessNumber);
407 
408     // Make sure that available is whatever left
409     assertTrue((limit - guessNumber) == avgLimiter.getAvailable());
410 
411     // Manually set avil to simulate that another thread call canExecute().
412     // It is simulated by consume().
413     avgLimiter.consume(-80);
414     assertTrue((limit - guessNumber + 80) == avgLimiter.getAvailable());
415 
416     // Now thread1 compensates 80
417     avgLimiter.consume(-80);
418     assertTrue(limit == avgLimiter.getAvailable());
419   }
420 }