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}