@Test public void testStateTransition() throws Exception { FakeTicker ticker = new FakeTicker(); int minimumRequestThreshold = 2; Duration circuitOpenWindow = Duration.ofSeconds(60); Duration counterSlidingWindow = Duration.ofSeconds(180); Duration counterUpdateInterval = Duration.ofMillis(1); Future<Object> successFuture = successFuture(); Future<Object> failedFuture = failedFuture(); CircuitBreaker circuitBreaker = new CircuitBreakerBuilder(remoteServiceName) .minimumRequestThreshold(minimumRequestThreshold) .circuitOpenWindow(circuitOpenWindow) .counterSlidingWindow(counterSlidingWindow) .counterUpdateInterval(counterUpdateInterval) .ticker(ticker) .build(); RemoteInvoker remoteInvoker = mock(RemoteInvoker.class); // return failed future when(remoteInvoker.invoke(any(), any(), any(), any(), any(), any())).thenReturn(failedFuture); CircuitBreakerMapping mapping = (eventLoop1, uri, options1, codec1, method, args1) -> circuitBreaker; CircuitBreakerRemoteInvoker stub = new CircuitBreakerRemoteInvoker(remoteInvoker, mapping); // CLOSED for (int i = 0; i < minimumRequestThreshold + 1; i++) { Future<Object> future = stub.invoke(eventLoop, uri, options, codec, methodA(), args); // The future is `failedFuture` itself assertThat(future.isSuccess(), is(false)); // This is not a CircuitBreakerException assertThat(future.cause(), is(not(instanceOf(FailFastException.class)))); ticker.advance(Duration.ofMillis(1).toNanos()); } // OPEN Future<Object> future1 = stub.invoke(eventLoop, uri, options, codec, methodA(), args); // The circuit is OPEN assertThat(future1.isSuccess(), is(false)); assertThat(future1.cause(), instanceOf(FailFastException.class)); assertThat(((FailFastException) future1.cause()).getCircuitBreaker(), is(circuitBreaker)); ticker.advance(circuitOpenWindow.toNanos()); // return success future when(remoteInvoker.invoke(any(), any(), any(), any(), any(), any())).thenReturn(successFuture); // HALF OPEN Future<Object> future2 = stub.invoke(eventLoop, uri, options, codec, methodA(), args); assertThat(future2.isSuccess(), is(true)); // CLOSED Future<Object> future3 = stub.invoke(eventLoop, uri, options, codec, methodA(), args); assertThat(future3.isSuccess(), is(true)); }
@SuppressWarnings("unchecked") private static <T> Future<T> failedFuture() { Future<T> future = mockFuture(); when(future.isSuccess()).thenReturn(false); when(future.cause()).thenReturn(new Exception()); return future; }
@Test public void testPerMethodScope() throws Exception { FakeTicker ticker = new FakeTicker(); int minimumRequestThreshold = 2; Duration circuitOpenWindow = Duration.ofSeconds(60); Duration counterSlidingWindow = Duration.ofSeconds(180); Duration counterUpdateInterval = Duration.ofMillis(1); Future<Object> successFuture = successFuture(); Future<Object> failedFuture = failedFuture(); Function<String, CircuitBreaker> factory = method -> new CircuitBreakerBuilder(remoteServiceName) .minimumRequestThreshold(minimumRequestThreshold) .circuitOpenWindow(circuitOpenWindow) .counterSlidingWindow(counterSlidingWindow) .counterUpdateInterval(counterUpdateInterval) .ticker(ticker) .build(); RemoteInvoker remoteInvoker = mock(RemoteInvoker.class); // Always return failed future for methodA when(remoteInvoker.invoke(any(), any(), any(), any(), eq(methodA()), any())) .thenReturn(failedFuture); // Always return success future for methodB when(remoteInvoker.invoke(any(), any(), any(), any(), eq(methodB()), any())) .thenReturn(successFuture); CircuitBreakerMapping mapping = new KeyedCircuitBreakerMapping<>(KeySelector.METHOD, factory::apply); CircuitBreakerRemoteInvoker stub = new CircuitBreakerRemoteInvoker(remoteInvoker, mapping); // CLOSED (methodA) for (int i = 0; i < minimumRequestThreshold + 1; i++) { stub.invoke(eventLoop, uri, options, codec, methodA(), args); ticker.advance(Duration.ofMillis(1).toNanos()); } // OPEN (methodA) Future<Object> future1 = stub.invoke(eventLoop, uri, options, codec, methodA(), args); assertThat(future1.isSuccess(), is(false)); assertThat(future1.cause(), instanceOf(FailFastException.class)); // CLOSED (methodB) Future<Object> future2 = stub.invoke(eventLoop, uri, options, codec, methodB(), args); assertThat(future2.isSuccess(), is(true)); }
@Override public final void operationComplete(Future<V> future) throws Exception { if (future.isSuccess()) { onSuccess(future.get()); } else { onFailure(future.cause()); } }
@SuppressWarnings("unchecked") private static <T> Future<T> successFuture() { Future<T> future = mockFuture(); when(future.isSuccess()).thenReturn(true); return future; }