001/*
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements.  See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership.  The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License.  You may obtain a copy of the License at
009 *
010 *     http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing, software
013 * distributed under the License is distributed on an "AS IS" BASIS,
014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015 * See the License for the specific language governing permissions and
016 * limitations under the License.
017 */
018package org.apache.hadoop.hbase.quotas;
019
020import static org.junit.jupiter.api.Assertions.assertEquals;
021import static org.junit.jupiter.api.Assertions.assertFalse;
022import static org.junit.jupiter.api.Assertions.assertNotEquals;
023import static org.junit.jupiter.api.Assertions.assertTrue;
024
025import java.util.concurrent.TimeUnit;
026import org.apache.hadoop.hbase.testclassification.RegionServerTests;
027import org.apache.hadoop.hbase.testclassification.SmallTests;
028import org.apache.hadoop.hbase.util.EnvironmentEdge;
029import org.apache.hadoop.hbase.util.EnvironmentEdgeManager;
030import org.apache.hadoop.hbase.util.ManualEnvironmentEdge;
031import org.junit.jupiter.api.Tag;
032import org.junit.jupiter.api.Test;
033
034/**
035 * Verify the behaviour of the Rate Limiter.
036 */
037@Tag(RegionServerTests.TAG)
038@Tag(SmallTests.TAG)
039public class TestRateLimiter {
040
041  @Test
042  public void testWaitIntervalTimeUnitSeconds() {
043    testWaitInterval(TimeUnit.SECONDS, 10, 100);
044  }
045
046  @Test
047  public void testWaitIntervalTimeUnitMinutes() {
048    testWaitInterval(TimeUnit.MINUTES, 10, 6000);
049  }
050
051  @Test
052  public void testWaitIntervalTimeUnitHours() {
053    testWaitInterval(TimeUnit.HOURS, 10, 360000);
054  }
055
056  @Test
057  public void testWaitIntervalTimeUnitDays() {
058    testWaitInterval(TimeUnit.DAYS, 10, 8640000);
059  }
060
061  private void testWaitInterval(final TimeUnit timeUnit, final long limit,
062    final long expectedWaitInterval) {
063    RateLimiter limiter = new AverageIntervalRateLimiter();
064    limiter.set(limit, timeUnit);
065
066    long nowTs = 0;
067    // consume all the available resources, one request at the time.
068    // the wait interval should be 0
069    for (int i = 0; i < (limit - 1); ++i) {
070      assertEquals(0, limiter.getWaitIntervalMs());
071      limiter.consume();
072      long waitInterval = limiter.waitInterval();
073      assertEquals(0, waitInterval);
074    }
075
076    for (int i = 0; i < (limit * 4); ++i) {
077      // There is one resource available, so we should be able to
078      // consume it without waiting.
079      limiter.setNextRefillTime(limiter.getNextRefillTime() - nowTs);
080      assertEquals(0, limiter.getWaitIntervalMs());
081      assertEquals(0, limiter.waitInterval());
082      limiter.consume();
083      // No more resources are available, we should wait for at least an interval.
084      long waitInterval = limiter.waitInterval();
085      assertEquals(expectedWaitInterval, waitInterval);
086
087      // set the nowTs to be the exact time when resources should be available again.
088      nowTs = waitInterval;
089
090      // artificially go into the past to prove that when too early we should fail.
091      long temp = nowTs + 500;
092      limiter.setNextRefillTime(limiter.getNextRefillTime() + temp);
093      assertNotEquals(0, limiter.getWaitIntervalMs());
094      // Roll back the nextRefillTime set to continue further testing
095      limiter.setNextRefillTime(limiter.getNextRefillTime() - temp);
096    }
097  }
098
099  @Test
100  public void testOverconsumptionAverageIntervalRefillStrategy() {
101    RateLimiter limiter = new AverageIntervalRateLimiter();
102    limiter.set(10, TimeUnit.SECONDS);
103
104    // 10 resources are available, but we need to consume 20 resources
105    // Verify that we have to wait at least 1.1sec to have 1 resource available
106    assertEquals(0, limiter.getWaitIntervalMs());
107    limiter.consume(20);
108    // We consumed twice the quota. Need to wait 1s to get back to 0, then another 100ms for the 1
109    assertEquals(1100, limiter.waitInterval(1));
110    // We consumed twice the quota. Need to wait 1s to get back to 0, then another 1s to get to 10
111    assertEquals(2000, limiter.waitInterval(10));
112
113    // Verify that after 1sec we need to wait for another 0.1sec to get a resource available
114    limiter.setNextRefillTime(limiter.getNextRefillTime() - 1000);
115    assertNotEquals(0, limiter.getWaitIntervalMs(1));
116    limiter.setNextRefillTime(limiter.getNextRefillTime() - 100);
117    // We've waited the full 1.1sec, should now have 1 available
118    assertEquals(0, limiter.getWaitIntervalMs(1));
119    assertEquals(0, limiter.waitInterval());
120  }
121
122  @Test
123  public void testOverconsumptionFixedIntervalRefillStrategy() throws InterruptedException {
124    RateLimiter limiter = new FixedIntervalRateLimiter();
125    limiter.set(10, TimeUnit.SECONDS);
126
127    // fix the current time in order to get the precise value of interval
128    EnvironmentEdge edge = new EnvironmentEdge() {
129      private final long ts = EnvironmentEdgeManager.currentTime();
130
131      @Override
132      public long currentTime() {
133        return ts;
134      }
135    };
136    EnvironmentEdgeManager.injectEdge(edge);
137    assertEquals(0, limiter.getWaitIntervalMs());
138    // 10 resources are available, but we need to consume 20 resources
139    limiter.consume(20);
140    // We over-consumed by 10. Since this is a fixed interval refill, where
141    // each interval we refill the full limit amount, we need to wait 2 intervals -- first
142    // interval gets us from -10 to 0, second gets us from 0 to 10, though we just want the 1.
143    assertEquals(2000, limiter.waitInterval(1));
144    EnvironmentEdgeManager.reset();
145
146    // Verify that after 1sec also no resource should be available
147    limiter.setNextRefillTime(limiter.getNextRefillTime() - 1000);
148    assertNotEquals(0, limiter.getWaitIntervalMs());
149    // Verify that after total 2sec the 10 resource is available
150    limiter.setNextRefillTime(limiter.getNextRefillTime() - 1000);
151    assertEquals(0, limiter.getWaitIntervalMs());
152    assertEquals(0, limiter.waitInterval());
153  }
154
155  @Test
156  public void testFixedIntervalResourceAvailability() throws Exception {
157    RateLimiter limiter = new FixedIntervalRateLimiter();
158    limiter.set(10, TimeUnit.SECONDS);
159
160    assertEquals(0, limiter.getWaitIntervalMs(10));
161    limiter.consume(3);
162    assertEquals(7, limiter.getAvailable());
163    assertNotEquals(0, limiter.getWaitIntervalMs(10));
164    limiter.setNextRefillTime(limiter.getNextRefillTime() - 1000);
165    assertEquals(0, limiter.getWaitIntervalMs(10));
166    assertEquals(10, limiter.getAvailable());
167  }
168
169  @Test
170  public void testLimiterBySmallerRate() throws InterruptedException {
171    // set limiter is 10 resources per seconds
172    RateLimiter limiter = new FixedIntervalRateLimiter();
173    limiter.set(10, TimeUnit.SECONDS);
174
175    int count = 0; // control the test count
176    while ((count++) < 10) {
177      // test will get 3 resources per 0.5 sec. so it will get 6 resources per sec.
178      limiter.setNextRefillTime(limiter.getNextRefillTime() - 500);
179      for (int i = 0; i < 3; i++) {
180        // 6 resources/sec < limit, so limiter.canExecute(nowTs, lastTs) should be true
181        assertEquals(limiter.getWaitIntervalMs(), 0);
182        limiter.consume();
183      }
184    }
185  }
186
187  @Test
188  public void testCanExecuteOfAverageIntervalRateLimiter() throws InterruptedException {
189    RateLimiter limiter = new AverageIntervalRateLimiter();
190    // when set limit is 100 per sec, this AverageIntervalRateLimiter will support at max 200 per
191    // sec
192    limiter.set(100, TimeUnit.SECONDS);
193    limiter.setNextRefillTime(EnvironmentEdgeManager.currentTime());
194    assertEquals(50, testCanExecuteByRate(limiter, 50));
195
196    // refill the avail to limit
197    limiter.set(100, TimeUnit.SECONDS);
198    limiter.setNextRefillTime(EnvironmentEdgeManager.currentTime());
199    assertEquals(100, testCanExecuteByRate(limiter, 100));
200
201    // refill the avail to limit
202    limiter.set(100, TimeUnit.SECONDS);
203    limiter.setNextRefillTime(EnvironmentEdgeManager.currentTime());
204    assertEquals(200, testCanExecuteByRate(limiter, 200));
205
206    // refill the avail to limit
207    limiter.set(100, TimeUnit.SECONDS);
208    limiter.setNextRefillTime(EnvironmentEdgeManager.currentTime());
209    assertEquals(200, testCanExecuteByRate(limiter, 500));
210  }
211
212  @Test
213  public void testCanExecuteOfFixedIntervalRateLimiter() throws InterruptedException {
214    RateLimiter limiter = new FixedIntervalRateLimiter();
215    // when set limit is 100 per sec, this FixedIntervalRateLimiter will support at max 100 per sec
216    limiter.set(100, TimeUnit.SECONDS);
217    limiter.setNextRefillTime(EnvironmentEdgeManager.currentTime());
218    assertEquals(50, testCanExecuteByRate(limiter, 50));
219
220    // refill the avail to limit
221    limiter.set(100, TimeUnit.SECONDS);
222    limiter.setNextRefillTime(EnvironmentEdgeManager.currentTime());
223    assertEquals(100, testCanExecuteByRate(limiter, 100));
224
225    // refill the avail to limit
226    limiter.set(100, TimeUnit.SECONDS);
227    limiter.setNextRefillTime(EnvironmentEdgeManager.currentTime());
228    assertEquals(100, testCanExecuteByRate(limiter, 200));
229  }
230
231  public int testCanExecuteByRate(RateLimiter limiter, int rate) {
232    int request = 0;
233    int count = 0;
234    while ((request++) < rate) {
235      limiter.setNextRefillTime(limiter.getNextRefillTime() - limiter.getTimeUnitInMillis() / rate);
236      if (limiter.getWaitIntervalMs() == 0) {
237        count++;
238        limiter.consume();
239      }
240    }
241    return count;
242  }
243
244  @Test
245  public void testRefillOfAverageIntervalRateLimiter() throws InterruptedException {
246    RateLimiter limiter = new AverageIntervalRateLimiter();
247    limiter.set(60, TimeUnit.SECONDS);
248    assertEquals(60, limiter.getAvailable());
249    // first refill, will return the number same with limit
250    assertEquals(60, limiter.refill(limiter.getLimit()));
251
252    limiter.consume(30);
253
254    // after 0.2 sec, refill should return 12
255    limiter.setNextRefillTime(limiter.getNextRefillTime() - 200);
256    assertEquals(12, limiter.refill(limiter.getLimit()));
257
258    // after 0.5 sec, refill should return 30
259    limiter.setNextRefillTime(limiter.getNextRefillTime() - 500);
260    assertEquals(30, limiter.refill(limiter.getLimit()));
261
262    // after 1 sec, refill should return 60
263    limiter.setNextRefillTime(limiter.getNextRefillTime() - 1000);
264    assertEquals(60, limiter.refill(limiter.getLimit()));
265
266    // after more than 1 sec, refill should return at max 60
267    limiter.setNextRefillTime(limiter.getNextRefillTime() - 3000);
268    assertEquals(60, limiter.refill(limiter.getLimit()));
269    limiter.setNextRefillTime(limiter.getNextRefillTime() - 5000);
270    assertEquals(60, limiter.refill(limiter.getLimit()));
271  }
272
273  @Test
274  public void testRefillOfFixedIntervalRateLimiter() throws InterruptedException {
275    RateLimiter limiter = new FixedIntervalRateLimiter();
276    limiter.set(60, TimeUnit.SECONDS);
277    assertEquals(60, limiter.getAvailable());
278    // first refill, will return the number same with limit
279    assertEquals(60, limiter.refill(limiter.getLimit()));
280
281    limiter.consume(30);
282
283    // after 0.2 sec, refill should return 0
284    limiter.setNextRefillTime(limiter.getNextRefillTime() - 200);
285    assertEquals(0, limiter.refill(limiter.getLimit()));
286
287    // after 0.5 sec, refill should return 0
288    limiter.setNextRefillTime(limiter.getNextRefillTime() - 500);
289    assertEquals(0, limiter.refill(limiter.getLimit()));
290
291    // after 1 sec, refill should return 60
292    limiter.setNextRefillTime(limiter.getNextRefillTime() - 1000);
293    assertEquals(60, limiter.refill(limiter.getLimit()));
294
295    // after more than 1 sec, refill should return at max 60
296    limiter.setNextRefillTime(limiter.getNextRefillTime() - 3000);
297    assertEquals(60, limiter.refill(limiter.getLimit()));
298    limiter.setNextRefillTime(limiter.getNextRefillTime() - 5000);
299    assertEquals(60, limiter.refill(limiter.getLimit()));
300  }
301
302  @Test
303  public void testUnconfiguredLimiters() throws InterruptedException {
304
305    ManualEnvironmentEdge testEdge = new ManualEnvironmentEdge();
306    EnvironmentEdgeManager.injectEdge(testEdge);
307    long limit = Long.MAX_VALUE;
308
309    // For unconfigured limiters, it is supposed to use as much as possible
310    RateLimiter avgLimiter = new AverageIntervalRateLimiter();
311    RateLimiter fixLimiter = new FixedIntervalRateLimiter();
312
313    assertEquals(limit, avgLimiter.getAvailable());
314    assertEquals(limit, fixLimiter.getAvailable());
315
316    assertEquals(0, avgLimiter.getWaitIntervalMs(limit));
317    avgLimiter.consume(limit);
318
319    assertEquals(0, fixLimiter.getWaitIntervalMs(limit));
320    fixLimiter.consume(limit);
321
322    // Make sure that available is Long.MAX_VALUE
323    assertEquals(limit, avgLimiter.getAvailable());
324    assertEquals(limit, fixLimiter.getAvailable());
325
326    // after 100 millseconds, it should be able to execute limit as well
327    testEdge.incValue(100);
328
329    assertEquals(0, avgLimiter.getWaitIntervalMs(limit));
330    avgLimiter.consume(limit);
331
332    assertEquals(0, fixLimiter.getWaitIntervalMs(limit));
333    fixLimiter.consume(limit);
334
335    // Make sure that available is Long.MAX_VALUE
336    assertEquals(limit, avgLimiter.getAvailable());
337    assertEquals(limit, fixLimiter.getAvailable());
338
339    EnvironmentEdgeManager.reset();
340  }
341
342  @Test
343  public void testExtremeLimiters() throws InterruptedException {
344
345    ManualEnvironmentEdge testEdge = new ManualEnvironmentEdge();
346    EnvironmentEdgeManager.injectEdge(testEdge);
347    long limit = Long.MAX_VALUE - 1;
348
349    RateLimiter avgLimiter = new AverageIntervalRateLimiter();
350    avgLimiter.set(limit, TimeUnit.SECONDS);
351    RateLimiter fixLimiter = new FixedIntervalRateLimiter();
352    fixLimiter.set(limit, TimeUnit.SECONDS);
353
354    assertEquals(limit, avgLimiter.getAvailable());
355    assertEquals(limit, fixLimiter.getAvailable());
356
357    assertEquals(0, avgLimiter.getWaitIntervalMs(limit / 2));
358    avgLimiter.consume(limit / 2);
359
360    assertEquals(0, fixLimiter.getWaitIntervalMs(limit / 2));
361    fixLimiter.consume(limit / 2);
362
363    // Make sure that available is whatever left
364    assertEquals((limit - (limit / 2)), avgLimiter.getAvailable());
365    assertEquals((limit - (limit / 2)), fixLimiter.getAvailable());
366
367    // after 100 millseconds, both should not be able to execute the limit
368    testEdge.incValue(100);
369
370    assertNotEquals(0, avgLimiter.getWaitIntervalMs(limit));
371    assertNotEquals(0, fixLimiter.getWaitIntervalMs(limit));
372
373    // after 500 millseconds, average interval limiter should be able to execute the limit
374    testEdge.incValue(500);
375    assertEquals(0, avgLimiter.getWaitIntervalMs(limit));
376    assertNotEquals(0, fixLimiter.getWaitIntervalMs(limit));
377
378    // Make sure that available is correct
379    assertEquals(limit, avgLimiter.getAvailable());
380    assertEquals((limit - (limit / 2)), fixLimiter.getAvailable());
381
382    // after 500 millseconds, both should be able to execute
383    testEdge.incValue(500);
384    assertEquals(0, avgLimiter.getWaitIntervalMs(limit));
385    assertEquals(0, fixLimiter.getWaitIntervalMs(limit));
386
387    // Make sure that available is Long.MAX_VALUE
388    assertEquals(limit, avgLimiter.getAvailable());
389    assertEquals(limit, fixLimiter.getAvailable());
390
391    EnvironmentEdgeManager.reset();
392  }
393
394  /*
395   * This test case is tricky. Basically, it simulates the following events: Thread-1 Thread-2 t0:
396   * canExecute(100) and consume(100) t1: canExecute(100), avail may be increased by 80 t2:
397   * consume(-80) as actual size is 20 It will check if consume(-80) can handle overflow correctly.
398   */
399  @Test
400  public void testLimiterCompensationOverflow() throws InterruptedException {
401
402    long limit = Long.MAX_VALUE - 1;
403    long guessNumber = 100;
404
405    // For unconfigured limiters, it is supposed to use as much as possible
406    RateLimiter avgLimiter = new AverageIntervalRateLimiter();
407    avgLimiter.set(limit, TimeUnit.SECONDS);
408
409    assertEquals(limit, avgLimiter.getAvailable());
410
411    // The initial guess is that 100 bytes.
412    assertEquals(0, avgLimiter.getWaitIntervalMs(guessNumber));
413    avgLimiter.consume(guessNumber);
414
415    // Make sure that available is whatever left
416    assertEquals((limit - guessNumber), avgLimiter.getAvailable());
417
418    // Manually set avil to simulate that another thread call canExecute().
419    // It is simulated by consume().
420    avgLimiter.consume(-80);
421    assertEquals((limit - guessNumber + 80), avgLimiter.getAvailable());
422
423    // Now thread1 compensates 80
424    avgLimiter.consume(-80);
425    assertEquals(limit, avgLimiter.getAvailable());
426  }
427
428  @Test
429  public void itRunsFullWithPartialRefillInterval() {
430    RateLimiter limiter = new FixedIntervalRateLimiter(100);
431    limiter.set(10, TimeUnit.SECONDS);
432    assertEquals(0, limiter.getWaitIntervalMs());
433
434    // Consume the quota
435    limiter.consume(10);
436
437    // Need to wait 1s to acquire another resource
438    long waitInterval = limiter.waitInterval(10);
439    assertTrue(900 < waitInterval);
440    assertTrue(1000 >= waitInterval);
441    // We need to wait 2s to acquire more than 10 resources
442    waitInterval = limiter.waitInterval(20);
443    assertTrue(1900 < waitInterval);
444    assertTrue(2000 >= waitInterval);
445
446    limiter.setNextRefillTime(limiter.getNextRefillTime() - 1000);
447    // We've waited the full interval, so we should now have 10
448    assertEquals(0, limiter.getWaitIntervalMs(10));
449    assertEquals(0, limiter.waitInterval());
450  }
451
452  @Test
453  public void itRunsPartialRefillIntervals() {
454    RateLimiter limiter = new FixedIntervalRateLimiter(100);
455    limiter.set(10, TimeUnit.SECONDS);
456    assertEquals(0, limiter.getWaitIntervalMs());
457
458    // Consume the quota
459    limiter.consume(10);
460
461    // Need to wait 1s to acquire another resource
462    long waitInterval = limiter.waitInterval(10);
463    assertTrue(900 < waitInterval);
464    assertTrue(1000 >= waitInterval);
465    // We need to wait 2s to acquire more than 10 resources
466    waitInterval = limiter.waitInterval(20);
467    assertTrue(1900 < waitInterval);
468    assertTrue(2000 >= waitInterval);
469    // We need to wait 0<=x<=100ms to acquire 1 resource
470    waitInterval = limiter.waitInterval(1);
471    assertTrue(0 < waitInterval);
472    assertTrue(100 >= waitInterval);
473
474    limiter.setNextRefillTime(limiter.getNextRefillTime() - 500);
475    // We've waited half the interval, so we should now have half available
476    assertEquals(0, limiter.getWaitIntervalMs(5));
477    assertEquals(0, limiter.waitInterval());
478  }
479
480  @Test
481  public void itRunsRepeatedPartialRefillIntervals() {
482    RateLimiter limiter = new FixedIntervalRateLimiter(100);
483    limiter.set(10, TimeUnit.SECONDS);
484    assertEquals(0, limiter.getWaitIntervalMs());
485    // Consume the quota
486    limiter.consume(10);
487    for (int i = 0; i < 100; i++) {
488      limiter.setNextRefillTime(limiter.getNextRefillTime() - 100); // free 1 resource
489      limiter.consume(1);
490      assertFalse(limiter.isAvailable(1)); // all resources consumed
491      assertTrue(limiter.isAvailable(0)); // not negative
492    }
493  }
494}