This is an automated email from the ASF dual-hosted git repository.

JingsongLi pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/paimon.git


The following commit(s) were added to refs/heads/master by this push:
     new 7c93b0307a [test] Stabilize 
SinkSavepointITCase.testRecoverFromSavepoint (#8741)
7c93b0307a is described below

commit 7c93b0307a0b4903f29beccab42e9b028ebedf2d
Author: Vova Kolmakov <[email protected]>
AuthorDate: Mon Jul 20 11:02:16 2026 +0700

    [test] Stabilize SinkSavepointITCase.testRecoverFromSavepoint (#8741)
---
 .../paimon/flink/sink/SinkSavepointITCase.java     | 35 ++++++++++++++++++++++
 1 file changed, 35 insertions(+)

diff --git 
a/paimon-flink/paimon-flink-common/src/test/java/org/apache/paimon/flink/sink/SinkSavepointITCase.java
 
b/paimon-flink/paimon-flink-common/src/test/java/org/apache/paimon/flink/sink/SinkSavepointITCase.java
index b1486deacb..717c4af291 100644
--- 
a/paimon-flink/paimon-flink-common/src/test/java/org/apache/paimon/flink/sink/SinkSavepointITCase.java
+++ 
b/paimon-flink/paimon-flink-common/src/test/java/org/apache/paimon/flink/sink/SinkSavepointITCase.java
@@ -74,10 +74,26 @@ public class SinkSavepointITCase extends AbstractTestBase {
             FailingFileIO.reset(failingName, 0, 1);
         }
 
+        // Bound the number of (expensive) stop-with-savepoint / restore 
cycles. On a starved CI
+        // runner each cycle pays the full Flink job-startup + restore cost, 
and the original
+        // "restart until FINISHED" loop is unbounded, so its wall-clock could 
balloon far past the
+        // @Timeout. After exercising a fixed number of recoveries we let the 
job run to completion
+        // uninterrupted, which keeps total runtime bounded while still 
covering savepoint recovery.
+        int savepointRounds = 0;
+        int maxSavepointRounds = 10;
+
         OUTER:
         while (true) {
             // start a new job or recover from savepoint
             JobClient jobClient = runRecoverFromSavepointJob(failingPath, 
savepointPath);
+
+            if (savepointRounds >= maxSavepointRounds) {
+                // enough recoveries exercised; let this job finish without 
more savepoints
+                waitForJobToFinish(jobClient);
+                break;
+            }
+            savepointRounds++;
+
             while (true) {
                 // wait for a random number of time before stopping with 
savepoint
                 Thread.sleep(random.nextInt(5000));
@@ -94,6 +110,12 @@ public class SinkSavepointITCase extends AbstractTestBase {
                                     .get();
                     break;
                 } catch (Exception e) {
+                    // Never swallow the interrupt raised by @Timeout: a bare 
InterruptedException
+                    // from Future.get() means the per-method deadline fired. 
Rethrow so the test
+                    // fails fast at ~180s instead of looping until the CI 
job-level timeout.
+                    if (ExceptionUtils.findThrowable(e, 
InterruptedException.class).isPresent()) {
+                        throw e;
+                    }
                     Optional<StopWithSavepointStoppingException> t =
                             ExceptionUtils.findThrowable(
                                     e, 
StopWithSavepointStoppingException.class);
@@ -120,6 +142,19 @@ public class SinkSavepointITCase extends AbstractTestBase {
         checkRecoverFromSavepointStreamingResult();
     }
 
+    private void waitForJobToFinish(JobClient jobClient) throws Exception {
+        JobStatus status;
+        while ((status = jobClient.getJobStatus().get()) != 
JobStatus.FINISHED) {
+            // Fail fast if the job dies instead of spinning until the 
@Timeout fires: this tells a
+            // dead job apart from a slow one, so a terminated job gives a 
clear error rather than
+            // an opaque timeout.
+            assertThat(status.isGloballyTerminalState())
+                    .as("job reached terminal state %s without finishing", 
status)
+                    .isFalse();
+            Thread.sleep(1000);
+        }
+    }
+
     private JobClient runRecoverFromSavepointJob(String failingPath, String 
savepointPath)
             throws Exception {
         Configuration conf = new Configuration();

Reply via email to