Fixing displayed retry attempts one-off error, when the error is non-retriable. Adding unit tests. TESTED=unit tests RELNOTES: None PiperOrigin-RevId: 170750220
diff --git a/src/main/java/com/google/devtools/build/lib/remote/Retrier.java b/src/main/java/com/google/devtools/build/lib/remote/Retrier.java index 8ce9d0a..197f2b5 100644 --- a/src/main/java/com/google/devtools/build/lib/remote/Retrier.java +++ b/src/main/java/com/google/devtools/build/lib/remote/Retrier.java
@@ -207,13 +207,14 @@ throw e; // Nested retries are always pass-through. } catch (StatusException | StatusRuntimeException e) { Status st = Status.fromThrowable(e); + int attempts = backoff.getRetryAttempts(); long delay = backoff.nextDelayMillis(); if (st.getCode() == Status.Code.CANCELLED && Thread.currentThread().isInterrupted()) { Thread.currentThread().interrupt(); throw new InterruptedException(); } if (delay < 0 || !isRetriable.apply(st)) { - throw new RetryException(st.asRuntimeException(), backoff.getRetryAttempts()); + throw new RetryException(st.asRuntimeException(), attempts); } sleep(delay); } catch (Exception e) {
diff --git a/src/main/java/com/google/devtools/build/lib/remote/Retrier2.java b/src/main/java/com/google/devtools/build/lib/remote/Retrier2.java index 3f817d0..e9a938b 100644 --- a/src/main/java/com/google/devtools/build/lib/remote/Retrier2.java +++ b/src/main/java/com/google/devtools/build/lib/remote/Retrier2.java
@@ -233,14 +233,14 @@ throw new CircuitBreakerException("Call failed in circuit breaker half open state.", 0, e); } + int attempts = backoff.getRetryAttempts(); if (!shouldRetry.test(e)) { - throw new RetryException2("Call failed with not retriable error.", - backoff.getRetryAttempts(), e); + throw new RetryException2("Call failed with not retriable error.", attempts, e); } final long delayMillis = backoff.nextDelayMillis(); if (delayMillis < 0) { - throw new RetryException2("Call failed after exhausting retry attempts: " - + backoff.getRetryAttempts(), backoff.getRetryAttempts(), e); + throw new RetryException2( + "Call failed after exhausting retry attempts: " + attempts, attempts, e); } sleeper.sleep(delayMillis); }
diff --git a/src/test/java/com/google/devtools/build/lib/remote/RemoteRetrierTest.java b/src/test/java/com/google/devtools/build/lib/remote/RemoteRetrierTest.java index 66de467..058a2df 100644 --- a/src/test/java/com/google/devtools/build/lib/remote/RemoteRetrierTest.java +++ b/src/test/java/com/google/devtools/build/lib/remote/RemoteRetrierTest.java
@@ -103,6 +103,17 @@ } @Test + public void testNonRetriableError() throws Exception { + Supplier<Backoff> s = + () -> new ExponentialBackoff(Duration.ofSeconds(1), Duration.ofSeconds(10), 2.0, 0.0, 2); + RemoteRetrier retrier = Mockito.spy(new RemoteRetrier(s, (e) -> false, + Retrier2.ALLOW_ALL_CALLS, Mockito.mock(Sleeper.class))); + when(fooMock.foo()).thenThrow(Status.Code.UNKNOWN.toStatus().asRuntimeException()); + assertThrows(retrier, 1); + Mockito.verify(fooMock, Mockito.times(1)).foo(); + } + + @Test public void testRepeatedRetriesReset() throws Exception { Supplier<Backoff> s = () -> new ExponentialBackoff(Duration.ofSeconds(1), Duration.ofSeconds(10), 2.0, 0.0, 2);
diff --git a/src/test/java/com/google/devtools/build/lib/remote/Retrier2Test.java b/src/test/java/com/google/devtools/build/lib/remote/Retrier2Test.java index 8e377f2..220c825 100644 --- a/src/test/java/com/google/devtools/build/lib/remote/Retrier2Test.java +++ b/src/test/java/com/google/devtools/build/lib/remote/Retrier2Test.java
@@ -47,6 +47,7 @@ private CircuitBreaker alwaysOpen; private static final Predicate<Exception> RETRY_ALL = (e) -> true; + private static final Predicate<Exception> RETRY_NONE = (e) -> false; @Before public void setup() { @@ -75,6 +76,26 @@ } @Test + public void retryShouldWorkNoRetries_failure() throws Exception { + // Test that a non-retriable error is not retried. + // All calls fail. + + Supplier<Backoff> s = () -> new ZeroBackoff(/*maxRetries=*/2); + Retrier2 r = new Retrier2(s, RETRY_NONE, alwaysOpen); + try { + r.execute(() -> { + throw new Exception("call failed"); + }); + fail("exception expected."); + } catch (RetryException2 e) { + assertThat(e.getAttempts()).isEqualTo(1); + } + + verify(alwaysOpen, times(1)).recordFailure(); + verify(alwaysOpen, never()).recordSuccess(); + } + + @Test public void retryShouldWork_success() throws Exception { // Test that a call is retried according to the backoff. // The last call succeeds.
diff --git a/src/test/java/com/google/devtools/build/lib/remote/RetrierTest.java b/src/test/java/com/google/devtools/build/lib/remote/RetrierTest.java index d55e2b6..dfce640 100644 --- a/src/test/java/com/google/devtools/build/lib/remote/RetrierTest.java +++ b/src/test/java/com/google/devtools/build/lib/remote/RetrierTest.java
@@ -95,6 +95,20 @@ } @Test + public void testNonRetriableError() throws Exception { + Retrier retrier = + Mockito.spy( + new Retrier( + Retrier.Backoff.exponential( + Duration.ofSeconds(1), Duration.ofSeconds(10), 2, 0, 2), + Retrier.DEFAULT_IS_RETRIABLE)); + Mockito.doNothing().when(retrier).sleep(Mockito.anyLong()); + when(fooMock.foo()).thenThrow(Status.Code.NOT_FOUND.toStatus().asRuntimeException()); + assertThrows(retrier, 1); + Mockito.verify(fooMock, Mockito.times(1)).foo(); + } + + @Test public void testRepeatedRetriesReset() throws Exception { Retrier retrier = Mockito.spy(