Skip to content

HDDS-11646. Intermittent timeout in TestXceiverClientMetrics - #11029

Open
shuan1026 wants to merge 2 commits into
apache:masterfrom
shuan1026:HDDS-11646
Open

HDDS-11646. Intermittent timeout in TestXceiverClientMetrics#11029
shuan1026 wants to merge 2 commits into
apache:masterfrom
shuan1026:HDDS-11646

Conversation

@shuan1026

@shuan1026 shuan1026 commented Aug 16, 2026

Copy link
Copy Markdown
Contributor

What changes were proposed in this pull request?

TestXceiverClientMetrics#testMetrics was tagged @Flaky("HDDS-11646") because of an intermittent waitFor TimeoutException.

Root cause: with the default STAND_ALONE pipeline, writes go through XceiverClientGrpc, whose sendCommandAsync already blocks the caller until the response arrives (shouldBlockAndWaitAsyncReply returns true for non-read-only requests). The test used a single background thread that fired 10 "async" writes serially and then slept 1s, so at most 1 request was ever in flight at a time, PendingOps floated back to 0 for most of each cycle, and the main thread's 100ms poll frequently missed the narrow non-zero window, causing the observed timeout.

This PR fixes the root cause by:

  • Replaces the single serialized sender thread with 10 concurrent sender threads, each continuously sending blocking writes until the pending spike is observed. With genuine concurrent in-flight requests, PendingOps stays non-zero for a sustained window instead of spiking for microseconds, so the poll reliably catches it.

  • computeResults is wrapped in Collections.synchronizedList for thread-safe concurrent writes, and each sender thread now calls countDown() on the shared CountDownLatch in a finally block so latch.await() can't hang if a thread exits via an exception.

  • The pending count increased poll now readsXceiverClientManager.getXceiverClientMetrics().getPendingContainerOpCountMetrics(...) directly instead of going through MetricsAsserts.getMetrics(SOURCE_NAME) on every tick; the poll interval is also tightened from 100ms to 10ms.

  • Adds a short retry before the final PendingOps == 0 / numPendingPutSmallFile == 0 assertions, absorbing the small window between a response future completing and its pending-metric decrement (two separate, non-atomic steps in XceiverClientGrpc's gRPC callback).

What is the link to the Apache JIRA

https://issues.apache.org/jira/browse/HDDS-11646

How was this patch tested?

@chihsuan chihsuan 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.

Thanks for digging into this one @shuan1026! Overall looks good.

I left a few inline comments, mainly about two behaviours the old test had that this version drops.

I also considered making this deterministic, but that seems to require a test hook in XceiverClientGrpc or the datanode. That feels beyond this Jira, so I think this direction is reasonable.


for (int i = 0; i < numSenderThreads; i++) {
Thread sendThread = new Thread(() -> {
try {

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.

I wonder if we should keep the try inside the loop? With the current structure, the first transient error permanently stops the sender, whereas the previous code retried.

@shuan1026 shuan1026 Aug 17, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done. try/catch is back inside the while loop.

}

Thread.sleep(1000);
} catch (Exception ignored) {

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.

Since we are already touching this block, would it make sense to keep the first failure somewhere and surface it if the wait times out? Right now, nothing is logged.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done. the first sender exception is kept in an AtomicReference.

getLongCounter("numPendingPutSmallFile", metric);

if (pendingOps > 0 && pendingPutSmallFileOps > 0) {
if (clientMetrics.getPendingContainerOpCountMetrics(

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.

The old predicate required both pendingOps > 0 and numPendingPutSmallFile > 0 but now only the per-type counter is checked. So a regression that stops incrementing the aggregate while still incrementing the per-type counter would pass. Should we keep both?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done. restored PendingOps > 0 && numPendingPutSmallFile > 0 via getMetrics(SOURCE_NAME), same as the original test.

The getSource NPE problem (the comment in HDDS-11646) only appears if this method is re-entered in the same JVM after close(), which was a side effect of the timeout + test-flaky rerun. With the flaky timeout fixed that path should not run, so I kept the original snapshot API. We can follow up on the metrics unregister lifecycle separately.


GenericTestUtils.waitFor(() ->
clientMetrics.getPendingContainerOpCountMetrics(
ContainerProtos.Type.PutSmallFile) == 0, 10, 5000);

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.

What do you think about waiting for both PendingOps and numPendingPutSmallFile to reach zero? We assert both below, and this avoids relying on their internal decrement order.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done. the drain waitFor now waits for both counters to reach 0.

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.

2 participants