From fbc1aaaf82520d735100a8b6758c7fbfec2fa3d8 Mon Sep 17 00:00:00 2001 From: Lawrence Qiu Date: Tue, 20 Oct 2026 19:19:35 +0000 Subject: [PATCH] test(gax): use FakeApiClock in DirectRetryingExecutorTest to avoid Thread.sleep Replace CurrentMillisClock and real Thread.sleep delays in DirectRetryingExecutorTest with FakeApiClock and a sleep() override that advances virtual time deterministically. This prevents testMaxRetriesExceeded from flaking with premature totalTimeout expiration on slow CI runners. Fixes #14625 --- .../AbstractRetryingExecutorTest.java | 7 ++++- .../retrying/DirectRetryingExecutorTest.java | 27 ++++++++++++++++--- 2 files changed, 30 insertions(+), 4 deletions(-) diff --git a/sdk-platform-java/gax-java/gax/src/test/java/com/google/api/gax/retrying/AbstractRetryingExecutorTest.java b/sdk-platform-java/gax-java/gax/src/test/java/com/google/api/gax/retrying/AbstractRetryingExecutorTest.java index 1be880ee581f..b6edd36ba2a8 100644 --- a/sdk-platform-java/gax-java/gax/src/test/java/com/google/api/gax/retrying/AbstractRetryingExecutorTest.java +++ b/sdk-platform-java/gax-java/gax/src/test/java/com/google/api/gax/retrying/AbstractRetryingExecutorTest.java @@ -45,6 +45,7 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; +import com.google.api.core.ApiClock; import com.google.api.core.ApiFuture; import com.google.api.core.NanoClock; import com.google.api.gax.retrying.FailingCallable.CustomException; @@ -102,6 +103,10 @@ protected abstract RetryingExecutorWithContext getExecutor( protected abstract RetryAlgorithm getAlgorithm( RetrySettings retrySettings, int apocalypseCountDown, RuntimeException apocalypseException); + protected ApiClock getClock() { + return NanoClock.getDefaultClock(); + } + protected void busyWaitForInitialResult(RetryingFuture future, java.time.Duration timeout) throws TimeoutException { Stopwatch watch = Stopwatch.createStarted(); @@ -356,7 +361,7 @@ void testPollExceptionByPollAlgorithm(boolean withCustomRetrySettings) throws Ex useContextRetrySettings ? withCustomRetrySettings ? FAILING_RETRY_SETTINGS : FAST_RETRY_SETTINGS : retrySettings, - NanoClock.getDefaultClock())); + getClock())); RetryingExecutorWithContext executor = getExecutor(retryAlgorithm); FailingCallable callable = new FailingCallable(6, "request", "FAILURE", tracer); diff --git a/sdk-platform-java/gax-java/gax/src/test/java/com/google/api/gax/retrying/DirectRetryingExecutorTest.java b/sdk-platform-java/gax-java/gax/src/test/java/com/google/api/gax/retrying/DirectRetryingExecutorTest.java index 28c0154e911f..f42c3fb20d77 100644 --- a/sdk-platform-java/gax-java/gax/src/test/java/com/google/api/gax/retrying/DirectRetryingExecutorTest.java +++ b/sdk-platform-java/gax-java/gax/src/test/java/com/google/api/gax/retrying/DirectRetryingExecutorTest.java @@ -29,13 +29,34 @@ */ package com.google.api.gax.retrying; -import com.google.api.core.CurrentMillisClock; +import com.google.api.core.ApiClock; +import com.google.api.gax.core.FakeApiClock; +import java.time.Duration; class DirectRetryingExecutorTest extends AbstractRetryingExecutorTest { + // ExponentialRetryAlgorithm and ExponentialPollAlgorithm only compute relative elapsed time + // deltas against firstAttemptStartTimeNanos (clock.nanoTime() - firstAttemptStartTimeNanos) + // and never treat 0L as a special sentinel value. + private final FakeApiClock clock = new FakeApiClock(0L); + + @Override + protected ApiClock getClock() { + return clock; + } + @Override protected RetryingExecutorWithContext getExecutor(RetryAlgorithm retryAlgorithm) { - return new DirectRetryingExecutor<>(retryAlgorithm); + return new DirectRetryingExecutor(retryAlgorithm) { + // Advance the fake clock instead of sleeping the current thread so that retry delays + // are simulated deterministically without wall-clock pauses on slow CI VMs. + @Override + protected void sleep(Duration delay) { + if (!delay.isZero() && !delay.isNegative()) { + clock.incrementNanoTime(delay.toNanos()); + } + } + }; } @Override @@ -43,6 +64,6 @@ protected RetryAlgorithm getAlgorithm( RetrySettings retrySettings, int apocalypseCountDown, RuntimeException apocalypseException) { return new RetryAlgorithm<>( new TestResultRetryAlgorithm(apocalypseCountDown, apocalypseException), - new ExponentialRetryAlgorithm(retrySettings, CurrentMillisClock.getDefaultClock())); + new ExponentialRetryAlgorithm(retrySettings, clock)); } }