rangareddy commented on code in PR #19485:
URL: https://github.com/apache/hudi/pull/19485#discussion_r3703601379
##########
hudi-utilities/src/test/java/org/apache/hudi/utilities/deltastreamer/HoodieDeltaStreamerTestBase.java:
##########
@@ -763,22 +766,47 @@ static HoodieInstant
assertCommitMetadataForIncrSource(String expected, String t
return lastInstant;
}
+ /**
+ * Polls {@code condition} until it holds, the deltastreamer future
finishes, or the timeout expires.
+ *
+ * <p>On timeout the last error the condition threw is attached to the
failure. Without it the only
+ * output is a bare {@link TimeoutException} pointing at this method,
which says nothing about which
+ * assertion never held - the reason HUDI-6843 stayed open: every report
of it looks identical.
+ */
static void waitTillCondition(Function<Boolean, Boolean> condition, Future
dsFuture, long timeoutInSecs) throws Exception {
- Future<Boolean> res = Executors.newSingleThreadExecutor().submit(() -> {
- boolean ret = false;
- while (!ret && !dsFuture.isDone()) {
- try {
- Thread.sleep(2000);
- ret = condition.apply(true);
- log.info("Condition completed successfully");
- } catch (Throwable error) {
- log.debug("Got error waiting for condition", error);
- ret = false;
+ AtomicReference<Throwable> lastError = new AtomicReference<>();
+ ExecutorService executor = Executors.newSingleThreadExecutor();
+ try {
+ Future<Boolean> res = executor.submit(() -> {
+ boolean ret = false;
+ while (!ret && !dsFuture.isDone()) {
+ try {
+ Thread.sleep(2000);
+ ret = condition.apply(true);
+ if (ret) {
+ log.info("Condition completed successfully");
+ }
+ } catch (Throwable error) {
Review Comment:
Good catch, and you are right — this was a real bug in my own change, not a
nit. Fixed in `541e7bb`.
`shutdownNow()` interrupts the polling thread, but `Thread.sleep` clears the
interrupt flag when it throws, and my `catch (Throwable)` swallowed the
`InterruptedException`, set `ret = false`, and re-entered the loop. `dsFuture`
never completes in the timeout case, so the loop had no other exit: **the
executor shutdown closed nothing in exactly the scenario it was added for.**
Now:
```java
while (!ret && !dsFuture.isDone() &&
!Thread.currentThread().isInterrupted()) {
try {
Thread.sleep(2000);
...
} catch (InterruptedException interrupted) {
Thread.currentThread().interrupt();
break;
} catch (Throwable error) {
lastError.set(error);
ret = false;
}
}
```
Handled before the catch-all, flag restored so the loop condition also sees
it, and deliberately **not** recorded as `lastError` — an interrupt is not a
condition failure, and recording it would have overwritten the assertion error
that the whole change exists to report.
Added `pollingStopsOnceTheWaitHasGivenUp`, which counts evaluations, lets
the wait fail, then waits two more poll intervals and asserts the count is
unchanged. With the interrupt handling reverted it fails with the leak
quantified:
```
AssertionFailedError: the polling thread should have stopped when the wait
gave up,
not carried on in the background ==> expected: <2> but was: <4>
```
Two extra polls after the wait had given up. Green with the fix.
##########
hudi-utilities/src/test/java/org/apache/hudi/utilities/deltastreamer/TestWaitTillCondition.java:
##########
@@ -0,0 +1,80 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.hudi.utilities.deltastreamer;
+
+import org.junit.jupiter.api.Test;
+
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.Future;
+
+import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+/**
+ * Covers {@code HoodieDeltaStreamerTestBase.TestHelpers#waitTillCondition},
the helper every
+ * continuous-mode deltastreamer test waits on.
+ *
+ * <p>HUDI-6843 is a flaky timeout in that wait whose only output was
+ * {@code java.util.concurrent.TimeoutException} at this method, with no
indication of which assertion in
+ * the condition never held - the condition's error was logged at debug and
discarded. That is why every
+ * report of the flake looks the same and none of them is actionable.
+ */
+class TestWaitTillCondition {
+
+ /** A deltastreamer future that never finishes, as a continuous-mode job
would be. */
+ private static final Future<?> RUNNING = new CompletableFuture<>();
+
+ @Test
+ void timeoutFailureNamesTheLastConditionFailure() {
+ String assertionText = "assertAtleastNDeltaCommits: expected at least 3
delta commits but got 2";
+
+ AssertionError error = assertThrows(AssertionError.class,
+ () -> HoodieDeltaStreamerTestBase.TestHelpers.waitTillCondition(
+ ignored -> {
+ throw new AssertionError(assertionText);
Review Comment:
Agreed, and fixed in `541e7bb` — a de-flaking test being timing-sensitive is
exactly the wrong failure mode.
The timeout is now 15s against the helper's 2s poll interval, so there is
room for roughly seven evaluations instead of barely one:
```java
/**
* The helper polls every 2s, so the timeout has to leave room for several
evaluations. A value close to
* one interval would make this test itself flaky on a loaded machine - if
the worker started late and the
* first evaluation landed after the timeout, nothing would have been
recorded to report.
*/
private static final int TIMEOUT_WITH_SLACK_SECS = 15;
```
The assertion on the message reuses the constant, so the two cannot drift
apart.
I did consider making the 2s poll interval injectable instead, to keep the
test fast, but that means widening the helper's signature purely for test
convenience — not worth it for a test that runs once. `TestWaitTillCondition`
is now 4 tests in ~27s.
Full re-validation after both fixes:
`TestHoodieDeltaStreamerWithMultiWriter` 5 tests green (151.7s) including the
target `testUpsertsContinuousModeWithMultipleWritersForConflicts`,
`TestWaitTillCondition` 4 green, checkstyle and `apache-rat:check` clean.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: [email protected]
For queries about this service, please contact Infrastructure at:
[email protected]