Skip to content

test(gax): use FakeApiClock and RecordingScheduler in ScheduledRetryingExecutorTest - #14706

Draft
lqiu96 wants to merge 2 commits into
googleapis:mainfrom
lqiu96:fix/gax-scheduled-retrying-executor-fake-clock-13535
Draft

lqiu96 wants to merge 2 commits into
googleapis:mainfrom
lqiu96:fix/gax-scheduled-retrying-executor-fake-clock-13535

Conversation

@lqiu96

@lqiu96 lqiu96 commented Oct 20, 2026

Copy link
Copy Markdown
Member

No description provided.

…read.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 googleapis#14625
…elpers

Use FakeApiClock and RecordingScheduler in ScheduledRetryingExecutorTest
for deterministic, sleep-free retry tests without modifying existing
asynchronous in-flight test methods.
Refactor RecordingScheduler to extend ScheduledThreadPoolExecutor
directly, eliminating Mockito dependencies and strict-stubbing friction.
Add Javadoc documentation for FakeApiClock and RecordingScheduler.

Fixes googleapis#13535

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request refactors the retrying executor tests to use deterministic simulated time instead of real wall-clock sleeps, replacing the Mockito-based RecordingScheduler with a concrete ScheduledThreadPoolExecutor that advances a FakeApiClock. DirectRetryingExecutorTest and ScheduledRetryingExecutorTest are updated to use this fake clock. The feedback suggests replacing the non-thread-safe ArrayList used for sleepDurations in RecordingScheduler with a CopyOnWriteArrayList to prevent potential concurrent modification issues during multi-threaded test execution.

final List<java.time.Duration> sleepDurations = new ArrayList<>();
final AtomicInteger iterationsCount = new AtomicInteger(0);
private final FakeApiClock clock;
private final List<Duration> sleepDurations = new ArrayList<>();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The sleepDurations list is implemented as a standard ArrayList, which is not thread-safe. Since RecordingScheduler extends ScheduledThreadPoolExecutor and can be called from multiple threads concurrently, concurrent calls to schedule can lead to race conditions or ConcurrentModificationException when writing to or reading from sleepDurations.

Using CopyOnWriteArrayList ensures thread safety for both writing (appending new durations) and reading (via getSleepDurations()). If any index-based operations are performed on this list concurrently, remember to snapshot the list to an array first to prevent IndexOutOfBoundsException.

Suggested change
private final List<Duration> sleepDurations = new ArrayList<>();
private final List<Duration> sleepDurations = new java.util.concurrent.CopyOnWriteArrayList<>();
References
  1. Before suggesting thread-safe collection alternatives (such as CopyOnWriteArrayList) to avoid lock contention, verify the existing definition of the collection to ensure it is not already thread-safe.
  2. To safely perform index-based operations on a CopyOnWriteArrayList that can be concurrently modified or shrunk, snapshot the list to an array (e.g., using toArray()) and perform all index-based accesses on the array snapshot to prevent IndexOutOfBoundsException.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant