@Override public TimedAttemptSettings createNextAttempt( Throwable prevThrowable, ResponseT prevResponse, TimedAttemptSettings prevSettings) { if (prevThrowable != null && prevThrowable instanceof DeadlineExceededException) { return TimedAttemptSettings.newBuilder() .setGlobalSettings(prevSettings.getGlobalSettings()) .setRetryDelay(prevSettings.getRetryDelay()) .setRpcTimeout(prevSettings.getRpcTimeout()) .setRandomizedRetryDelay(DEADLINE_SLEEP_DURATION) .setAttemptCount(prevSettings.getAttemptCount() + 1) .setFirstAttemptStartTimeNanos(prevSettings.getFirstAttemptStartTimeNanos()) .build(); } return null; }
@Test public void testCancelByRetryingAlgorithm() throws Exception { FailingCallable callable = new FailingCallable(6, "FAILURE", tracer); RetryingExecutorWithContext<String> executor = getExecutor(getAlgorithm(FAST_RETRY_SETTINGS, 5, new CancellationException())); RetryingFuture<String> future = executor.createFuture(callable, retryingContext); future.setAttemptFuture(executor.submit(future)); assertFutureCancel(future); assertEquals(4, future.getAttemptSettings().getAttemptCount()); verify(tracer, times(5)).attemptStarted(anyInt()); // Pre-apocalypse failures verify(tracer, times(4)).attemptFailed(any(Throwable.class), any(Duration.class)); // Apocalypse failure verify(tracer, times(1)).attemptFailedRetriesExhausted(any(CancellationException.class)); verifyNoMoreInteractions(tracer); }
@Test public void testMaxRetriesExceeded() throws Exception { FailingCallable callable = new FailingCallable(6, "FAILURE", tracer); RetryingExecutorWithContext<String> executor = getExecutor(getAlgorithm(FAST_RETRY_SETTINGS, 0, null)); RetryingFuture<String> future = executor.createFuture(callable, retryingContext); future.setAttemptFuture(executor.submit(future)); assertFutureFail(future, CustomException.class); assertEquals(5, future.getAttemptSettings().getAttemptCount()); verify(tracer, times(6)).attemptStarted(anyInt()); verify(tracer, times(5)).attemptFailed(any(Throwable.class), any(Duration.class)); verify(tracer, times(1)).attemptFailedRetriesExhausted(any(Throwable.class)); verifyNoMoreInteractions(tracer); }
@Test public void testUnexpectedExceptionFromRetryAlgorithm() throws Exception { FailingCallable callable = new FailingCallable(6, "FAILURE", tracer); RetryingExecutorWithContext<String> executor = getExecutor(getAlgorithm(FAST_RETRY_SETTINGS, 5, new RuntimeException())); RetryingFuture<String> future = executor.createFuture(callable, retryingContext); future.setAttemptFuture(executor.submit(future)); assertFutureFail(future, RuntimeException.class); assertEquals(4, future.getAttemptSettings().getAttemptCount()); verify(tracer, times(5)).attemptStarted(anyInt()); // Pre-apocalypse failures verify(tracer, times(4)).attemptFailed(any(Throwable.class), any(Duration.class)); // Apocalypse failure verify(tracer, times(1)).attemptPermanentFailure(any(RuntimeException.class)); verifyNoMoreInteractions(tracer); }
@Test public void testSuccessWithFailures() throws Exception { FailingCallable callable = new FailingCallable(5, "SUCCESS", tracer); RetryingExecutorWithContext<String> executor = getExecutor(getAlgorithm(FAST_RETRY_SETTINGS, 0, null)); RetryingFuture<String> future = executor.createFuture(callable, retryingContext); future.setAttemptFuture(executor.submit(future)); assertFutureSuccess(future); assertEquals(5, future.getAttemptSettings().getAttemptCount()); verify(tracer, times(6)).attemptStarted(anyInt()); verify(tracer, times(5)).attemptFailed(any(Throwable.class), any(Duration.class)); verify(tracer, times(1)).attemptSucceeded(); verifyNoMoreInteractions(tracer); }
@Test public void testSuccess() throws Exception { FailingCallable callable = new FailingCallable(0, "SUCCESS", tracer); RetryingExecutorWithContext<String> executor = getExecutor(getAlgorithm(FAST_RETRY_SETTINGS, 0, null)); RetryingFuture<String> future = executor.createFuture(callable, retryingContext); future.setAttemptFuture(executor.submit(future)); assertFutureSuccess(future); assertEquals(0, future.getAttemptSettings().getAttemptCount()); verify(tracer, times(1)).attemptStarted(0); verify(tracer, times(1)).attemptSucceeded(); verifyNoMoreInteractions(tracer); }
/** * Returns {@code true} if another attempt should be made, or {@code false} otherwise. * * @param nextAttemptSettings attempt settings, which will be used for the next attempt, if * accepted * @return {@code true} if {@code nextAttemptSettings} does not exceed either maxAttempts limit or * totalTimeout limit, or {@code false} otherwise */ @Override public boolean shouldRetry(TimedAttemptSettings nextAttemptSettings) { RetrySettings globalSettings = nextAttemptSettings.getGlobalSettings(); long totalTimeSpentNanos = clock.nanoTime() - nextAttemptSettings.getFirstAttemptStartTimeNanos() + nextAttemptSettings.getRandomizedRetryDelay().toNanos(); return totalTimeSpentNanos <= globalSettings.getTotalTimeout().toNanos() && (globalSettings.getMaxAttempts() <= 0 || nextAttemptSettings.getAttemptCount() < globalSettings.getMaxAttempts()); }
/** * Returns {@code true} if another attempt should be made, or {@code false} otherwise. * * @param nextAttemptSettings attempt settings, which will be used for the next attempt, if * accepted * @return {@code true} if {@code nextAttemptSettings} does not exceed either maxAttempts limit or * totalTimeout limit, or {@code false} otherwise */ @Override public boolean shouldRetry(TimedAttemptSettings nextAttemptSettings) { RetrySettings globalSettings = nextAttemptSettings.getGlobalSettings(); long totalTimeSpentNanos = clock.nanoTime() - nextAttemptSettings.getFirstAttemptStartTimeNanos() + nextAttemptSettings.getRandomizedRetryDelay().toNanos(); return totalTimeSpentNanos <= globalSettings.getTotalTimeout().toNanos() && (globalSettings.getMaxAttempts() <= 0 || nextAttemptSettings.getAttemptCount() < globalSettings.getMaxAttempts()); }
@Test public void testTotalTimeoutExceeded() throws Exception { RetrySettings retrySettings = FAST_RETRY_SETTINGS .toBuilder() .setInitialRetryDelay(Duration.ofMillis(Integer.MAX_VALUE)) .setMaxRetryDelay(Duration.ofMillis(Integer.MAX_VALUE)) .build(); RetryingExecutorWithContext<String> executor = getExecutor(getAlgorithm(retrySettings, 0, null)); FailingCallable callable = new FailingCallable(6, "FAILURE", tracer); RetryingFuture<String> future = executor.createFuture(callable, retryingContext); future.setAttemptFuture(executor.submit(future)); assertFutureFail(future, CustomException.class); assertTrue(future.getAttemptSettings().getAttemptCount() < 4); verify(tracer, times(1)).attemptStarted(anyInt()); verify(tracer, times(1)).attemptFailedRetriesExhausted(any(Throwable.class)); verifyNoMoreInteractions(tracer); }
@Override public TimedAttemptSettings createNextAttempt( Throwable prevThrowable, ResponseT prevResponse, TimedAttemptSettings prevSettings) { if (prevThrowable != null && prevThrowable instanceof DeadlineExceededException) { return TimedAttemptSettings.newBuilder() .setGlobalSettings(prevSettings.getGlobalSettings()) .setRetryDelay(prevSettings.getRetryDelay()) .setRpcTimeout(prevSettings.getRpcTimeout()) .setRandomizedRetryDelay(DEADLINE_SLEEP_DURATION) .setAttemptCount(prevSettings.getAttemptCount() + 1) .setFirstAttemptStartTimeNanos(prevSettings.getFirstAttemptStartTimeNanos()) .build(); } return null; }
if (prevSettings.getAttemptCount() > 0) { newRetryDelay = (long) (settings.getRetryDelayMultiplier() * prevSettings.getRetryDelay().toMillis()); .setRpcTimeout(Duration.ofMillis(newRpcTimeout)) .setRandomizedRetryDelay(Duration.ofMillis(nextRandomLong(newRetryDelay))) .setAttemptCount(prevSettings.getAttemptCount() + 1) .setOverallAttemptCount(prevSettings.getOverallAttemptCount() + 1) .setFirstAttemptStartTimeNanos(prevSettings.getFirstAttemptStartTimeNanos())
if (prevSettings.getAttemptCount() > 0) { newRetryDelay = (long) (settings.getRetryDelayMultiplier() * prevSettings.getRetryDelay().toMillis()); .setRpcTimeout(Duration.ofMillis(newRpcTimeout)) .setRandomizedRetryDelay(Duration.ofMillis(nextRandomLong(newRetryDelay))) .setAttemptCount(prevSettings.getAttemptCount() + 1) .setOverallAttemptCount(prevSettings.getOverallAttemptCount() + 1) .setFirstAttemptStartTimeNanos(prevSettings.getFirstAttemptStartTimeNanos())
@Test public void testPollExceptionByPollAlgorithm() throws Exception { RetrySettings retrySettings = FAST_RETRY_SETTINGS .toBuilder() .setInitialRetryDelay(Duration.ofMillis(Integer.MAX_VALUE)) .setMaxRetryDelay(Duration.ofMillis(Integer.MAX_VALUE)) .build(); RetryAlgorithm<String> retryAlgorithm = new RetryAlgorithm<>( new TestResultRetryAlgorithm<String>(0, null), new ExponentialPollAlgorithm(retrySettings, NanoClock.getDefaultClock())); RetryingExecutorWithContext<String> executor = getExecutor(retryAlgorithm); FailingCallable callable = new FailingCallable(6, "FAILURE", tracer); RetryingFuture<String> future = executor.createFuture(callable, retryingContext); future.setAttemptFuture(executor.submit(future)); assertFutureFail(future, PollException.class); assertTrue(future.getAttemptSettings().getAttemptCount() < 4); verify(tracer, times(1)).attemptStarted(anyInt()); verify(tracer, times(1)).attemptPermanentFailure(any(PollException.class)); verifyNoMoreInteractions(tracer); }
assertEquals(15, future.getAttemptSettings().getAttemptCount()); assertTrue("checks is equal to " + checks, checks > 1 && checks <= maxRetries); localExecutor.shutdownNow();
@Test public void testCreateFirstAttempt() { TimedAttemptSettings attempt = algorithm.createFirstAttempt(); // Checking only the most core values, to not make this test too implementation specific. assertEquals(0, attempt.getAttemptCount()); assertEquals(0, attempt.getOverallAttemptCount()); assertEquals(Duration.ZERO, attempt.getRetryDelay()); assertEquals(Duration.ZERO, attempt.getRandomizedRetryDelay()); assertEquals(Duration.ofMillis(1L), attempt.getRpcTimeout()); assertEquals(Duration.ZERO, attempt.getRetryDelay()); }
@Test public void testCreateNextAttempt() { TimedAttemptSettings firstAttempt = algorithm.createFirstAttempt(); TimedAttemptSettings secondAttempt = algorithm.createNextAttempt(firstAttempt); // Checking only the most core values, to not make this test too implementation specific. assertEquals(1, secondAttempt.getAttemptCount()); assertEquals(1, secondAttempt.getOverallAttemptCount()); assertEquals(Duration.ofMillis(1L), secondAttempt.getRetryDelay()); assertEquals(Duration.ofMillis(1L), secondAttempt.getRandomizedRetryDelay()); assertEquals(Duration.ofMillis(2L), secondAttempt.getRpcTimeout()); TimedAttemptSettings thirdAttempt = algorithm.createNextAttempt(secondAttempt); assertEquals(2, thirdAttempt.getAttemptCount()); assertEquals(Duration.ofMillis(2L), thirdAttempt.getRetryDelay()); assertEquals(Duration.ofMillis(2L), thirdAttempt.getRandomizedRetryDelay()); assertEquals(Duration.ofMillis(4L), thirdAttempt.getRpcTimeout()); }
assertTrue(future.getAttemptSettings().getAttemptCount() > 0); assertFutureCancel(future); localExecutor.shutdownNow();
assertEquals(15, future.getAttemptSettings().getAttemptCount()); assertTrue(failedAttempts > 0); localExecutor.shutdownNow();
@Test public void testSuccessWithFailuresPeekGetAttempt() throws Exception { FailingCallable callable = new FailingCallable(5, "SUCCESS", tracer); RetryingExecutorWithContext<String> executor = getExecutor(getAlgorithm(FAST_RETRY_SETTINGS, 0, null)); RetryingFuture<String> future = executor.createFuture(callable, retryingContext); assertNull(future.peekAttemptResult()); assertSame(future.peekAttemptResult(), future.peekAttemptResult()); assertFalse(future.getAttemptResult().isDone()); assertFalse(future.getAttemptResult().isCancelled()); Exception exception = null; try { future.get(1L, TimeUnit.MILLISECONDS); } catch (TimeoutException e) { exception = e; } assertNotNull(exception); future.setAttemptFuture(executor.submit(future)); assertFutureSuccess(future); assertEquals(5, future.getAttemptSettings().getAttemptCount()); }