@Override public void continueOrPropagate(RetryableException exception) { failedAttemptsForCurrentServer.increment(); if (backoffStrategy.backoff(failedAttemptsForCurrentServer.get())) { // Use same server again. log.info("{}: {}. Attempt #{} failed for server {}. Retrying the same server.", exception.getCause(), exception.getMessage(), failedAttemptsForCurrentServer.get(), servers.get(currentServer.get())); } else { // Use next server or fail if all servers have failed. failedServers.increment(); if (failedServers.get() >= servers.size()) { // Attempted to call all servers - propagate exception. // Note: Not resetting state here since Feign calls clone() before re-using this retryer. throw exception; } else { // Call next server in list. log.info("{}: {}. Server #{} ({}) failed {} times - trying next server", exception.getCause(), exception.getMessage(), failedServers.get(), servers.get(currentServer.get()), failedAttemptsForCurrentServer.get()); currentServer.set((currentServer.get() + 1) % servers.size()); failedAttemptsForCurrentServer.set(0); } } }
@Test @Theory public void testConnectionError_whenOneCallFailsThenSubsequentNewCallsCanStillSucceed( @FromDataPoints("AllStrategies") FailoverTestCase failoverTestCase) throws Exception { TestService proxy = failoverTestCase.getProxy(); // Call fails when servers are down. failoverTestCase.server1.shutdown(); failoverTestCase.server2.shutdown(); try { proxy.string(); fail(); } catch (RetryableException e) { assertThat(e.getMessage(), startsWith("Failed to complete the request due to an IOException")); } // Subsequent call (with the same proxy instance) succeeds. MockWebServer anotherServer1 = new MockWebServer(); // Not a @Rule so we can control start/stop/port explicitly anotherServer1.start(failoverTestCase.server1.getPort()); anotherServer1.enqueue(new MockResponse().setBody("\"foo\"")); assertThat(proxy.string(), is("foo")); anotherServer1.shutdown(); }