@Test public void shouldNotEvictAccessedBucket() throws Exception { FakeTimeService time = new FakeTimeService(0); ThrottlingFilter filter = new ThrottlingFilter(time, 2, duration("3 seconds"), DEFAULT_PARTITION_EXPR); Context context = mock(Context.class); Request request; // Here we access the same bucket every 2 seconds : enough time to add a refilled token but not // enough for the // bucket to be evicted. Handler handler1 = mock(Handler.class, "handler1"); request = new Request(); filter.filter(context, request, handler1); verify(handler1).handle(context, request); time.advance(duration("2 seconds")); Handler handler2 = mock(Handler.class, "handler2"); request = new Request(); filter.filter(context, request, handler2); verify(handler2).handle(context, request); assertThat(filter.getBucketsStats().evictionCount()).isEqualTo(0); time.advance(duration("2 seconds")); Handler handler3 = mock(Handler.class, "handler3"); request = new Request(); filter.filter(context, request, handler3); verify(handler3).handle(context, request); assertThat(filter.getBucketsStats().evictionCount()).isEqualTo(0); }
@Test public void shouldThrottleRequests() throws Exception { FakeTimeService time = new FakeTimeService(0); ThrottlingFilter filter = new ThrottlingFilter(time, 1, duration("3 seconds"), DEFAULT_PARTITION_EXPR); // This one has to call the handler as there are enough tokens in the bucket. Handler handler1 = mock(Handler.class, "handler1"); filter.filter(mock(Context.class), new Request(), handler1); verify(handler1).handle(any(Context.class), any(Request.class)); time.advance(duration("2 seconds")); // This one does not have to be called as there is no token anymore in the bucket. Handler handler2 = mock(Handler.class, "handler2"); Promise<Response, NeverThrowsException> promise2 = filter.filter(mock(Context.class), new Request(), handler2); verifyZeroInteractions(handler2); assertThat(promise2.get().getStatus()).isEqualTo(Status.TOO_MANY_REQUESTS); time.advance(duration("4 seconds")); // This one has to call the handler as the bucket has been refilled. Handler handler3 = mock(Handler.class, "handler3"); filter.filter(mock(Context.class), new Request(), handler3); verify(handler3).handle(any(Context.class), any(Request.class)); }
@Test public void shouldThrottleConcurrentRequests() throws Exception { CountDownLatch latch1 = new CountDownLatch(1); CountDownLatch latch2 = new CountDownLatch(1); FakeTimeService time = new FakeTimeService(0); final ThrottlingFilter filter = new ThrottlingFilter(time, 1, duration("3 seconds"), DEFAULT_PARTITION_EXPR); // This one has to be called as there are enough tokens in the bucket. final Handler handler1 = new LatchHandler(latch1, latch2); Runnable r = new Runnable() { @Override public void run() { filter.filter(mock(Context.class), new Request(), handler1); } }; Thread t1 = new Thread(r); t1.setName("Filter for request #1"); t1.start(); latch2.await(); time.advance(duration("2 seconds")); try { // This one does not have to be called as there no token anymore in the bucket. Handler handler2 = mock(Handler.class, "handler2"); Promise<Response, NeverThrowsException> promise2 = filter.filter(mock(Context.class), new Request(), handler2); verifyZeroInteractions(handler2); Response response = promise2.get(20, TimeUnit.SECONDS); assertThat(response.getStatus()).isEqualTo(Status.TOO_MANY_REQUESTS); assertThat(response.getHeaders().getFirst("Retry-After")).isEqualTo("1"); } finally { latch1.countDown(); t1.join(); } }
@Test public void shouldEvictObsoleteBucket() throws Exception { FakeTimeService time = new FakeTimeService(0); ThrottlingFilter filter = new ThrottlingFilter(time, 1, duration("3 seconds"), DEFAULT_PARTITION_EXPR); Context context = mock(Context.class); // Call the filter in order to trigger the bucket creation Handler handler1 = mock(Handler.class, "handler1"); filter.filter(context, new Request(), handler1); verify(handler1).handle(any(Exchange.class), any(Request.class)); time.advance(duration("4 seconds")); // After 3 seconds, the previously bucket should be discarded, force a new call so that the // cache will clean its // internal structure Handler handler2 = mock(Handler.class, "handler2"); filter.filter(context, new Request(), handler2); verify(handler2).handle(any(Exchange.class), any(Request.class)); assertThat(filter.getBucketsStats().evictionCount()).isEqualTo(1); }