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

davidzollo pushed a commit to branch dev
in repository https://gitbox.apache.org/repos/asf/seatunnel.git


The following commit(s) were added to refs/heads/dev by this push:
     new 54a176c5de [Fix][Zeta] Make finished jobs API tolerate missing history 
data (#11269)
54a176c5de is described below

commit 54a176c5decf97a480293ca190e76bc13326b617
Author: Jast <[email protected]>
AuthorDate: Tue Jul 7 23:48:54 2026 +0800

    [Fix][Zeta] Make finished jobs API tolerate missing history data (#11269)
---
 .../engine/server/rest/service/JobInfoService.java | 50 +++++++++++++---------
 .../rest/service/JobInfoServiceNullSafetyTest.java | 27 ++++++++++++
 2 files changed, 57 insertions(+), 20 deletions(-)

diff --git 
a/seatunnel-engine/seatunnel-engine-server/src/main/java/org/apache/seatunnel/engine/server/rest/service/JobInfoService.java
 
b/seatunnel-engine/seatunnel-engine-server/src/main/java/org/apache/seatunnel/engine/server/rest/service/JobInfoService.java
index c54352afac..13e243f9fb 100644
--- 
a/seatunnel-engine/seatunnel-engine-server/src/main/java/org/apache/seatunnel/engine/server/rest/service/JobInfoService.java
+++ 
b/seatunnel-engine/seatunnel-engine-server/src/main/java/org/apache/seatunnel/engine/server/rest/service/JobInfoService.java
@@ -32,7 +32,6 @@ import 
org.apache.seatunnel.engine.core.job.JobImmutableInformation;
 import org.apache.seatunnel.engine.core.job.JobInfo;
 import org.apache.seatunnel.engine.server.SeaTunnelServer;
 import org.apache.seatunnel.engine.server.master.JobHistoryService.JobState;
-import org.apache.seatunnel.engine.server.operation.GetJobMetricsOperation;
 import org.apache.seatunnel.engine.server.operation.GetJobStatusOperation;
 import org.apache.seatunnel.engine.server.rest.ConfigFormat;
 import org.apache.seatunnel.engine.server.rest.RestConstant;
@@ -121,9 +120,8 @@ public class JobInfoService extends BaseService {
         IMap<Long, JobDAGInfo> finishedJobDAGInfo =
                 
nodeEngine.getHazelcastInstance().getMap(Constant.IMAP_FINISHED_JOB_VERTEX_INFO);
 
-        SeaTunnelServer seaTunnelServer = getSeaTunnelServer(true);
-
         return finishedJob.values().stream()
+                .filter(java.util.Objects::nonNull)
                 .filter(
                         jobState -> {
                             if (state.isEmpty()) {
@@ -131,31 +129,43 @@ public class JobInfoService extends BaseService {
                             }
                             return 
jobState.getJobStatus().name().equals(state.toUpperCase());
                         })
-                .sorted(Comparator.comparing(JobState::getFinishTime, 
Comparator.reverseOrder()))
+                .sorted(
+                        Comparator.comparing(
+                                JobState::getFinishTime,
+                                
Comparator.nullsLast(Comparator.reverseOrder())))
                 .map(
                         jobState -> {
                             Long jobId = jobState.getJobId();
-                            String jobMetrics;
-                            if (seaTunnelServer == null) {
-                                jobMetrics =
-                                        (String)
-                                                
NodeEngineUtil.sendOperationToMasterNode(
-                                                                nodeEngine,
-                                                                new 
GetJobMetricsOperation(jobId))
-                                                        .join();
-                            } else {
-                                jobMetrics =
-                                        seaTunnelServer
-                                                .getCoordinatorService()
-                                                .getJobMetrics(jobId)
-                                                .toJsonString();
-                            }
                             return getJobInfoJson(
-                                    jobState, jobMetrics, 
finishedJobDAGInfo.get(jobId));
+                                    jobState,
+                                    getFinishedJobMetricsJson(jobId),
+                                    getFinishedJobDAGInfo(finishedJobDAGInfo, 
jobId));
                         })
                 .collect(JsonArray::new, JsonArray::add, JsonArray::add);
     }
 
+    private String getFinishedJobMetricsJson(Long jobId) {
+        try {
+            IMap<Long, JobMetrics> finishedJobMetrics =
+                    
nodeEngine.getHazelcastInstance().getMap(Constant.IMAP_FINISHED_JOB_METRICS);
+            JobMetrics metrics = finishedJobMetrics.getOrDefault(jobId, 
JobMetrics.empty());
+            return metrics == null ? JobMetrics.empty().toJsonString() : 
metrics.toJsonString();
+        } catch (Throwable t) {
+            log.warn("Failed to load finished job metrics for job {}: {}", 
jobId, t.getMessage());
+            return JobMetrics.empty().toJsonString();
+        }
+    }
+
+    private JobDAGInfo getFinishedJobDAGInfo(
+            IMap<Long, JobDAGInfo> finishedJobDAGInfo, Long jobId) {
+        try {
+            return finishedJobDAGInfo.get(jobId);
+        } catch (Throwable t) {
+            log.warn("Failed to load finished job DAG for job {}: {}", jobId, 
t.getMessage());
+            return null;
+        }
+    }
+
     public JsonArray getRunningJobsJson() {
         IMap<Long, JobInfo> values = getRunningJobInfoMap();
         SeaTunnelServer seaTunnelServer = getSeaTunnelServer(true);
diff --git 
a/seatunnel-engine/seatunnel-engine-server/src/test/java/org/apache/seatunnel/engine/server/rest/service/JobInfoServiceNullSafetyTest.java
 
b/seatunnel-engine/seatunnel-engine-server/src/test/java/org/apache/seatunnel/engine/server/rest/service/JobInfoServiceNullSafetyTest.java
index 19ce8ce314..151f60b4ed 100644
--- 
a/seatunnel-engine/seatunnel-engine-server/src/test/java/org/apache/seatunnel/engine/server/rest/service/JobInfoServiceNullSafetyTest.java
+++ 
b/seatunnel-engine/seatunnel-engine-server/src/test/java/org/apache/seatunnel/engine/server/rest/service/JobInfoServiceNullSafetyTest.java
@@ -29,10 +29,12 @@ import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
 
 import com.hazelcast.core.HazelcastInstance;
+import com.hazelcast.internal.json.JsonArray;
 import com.hazelcast.internal.json.JsonObject;
 import com.hazelcast.map.IMap;
 import com.hazelcast.spi.impl.NodeEngineImpl;
 
+import java.util.Arrays;
 import java.util.Collections;
 
 import static org.mockito.Mockito.mock;
@@ -134,4 +136,29 @@ class JobInfoServiceNullSafetyTest {
         Assertions.assertNotNull(result);
         Assertions.assertEquals(jobId.toString(), 
result.getString(RestConstant.JOB_ID, null));
     }
+
+    @Test
+    void shouldListFinishedJobsWhenFinishTimeMetricsOrDagAreMissing() {
+        Long jobWithFinishTime = 2L;
+        JobHistoryService.JobState missingFinishTime = buildJobState(jobId, 
1000L, null);
+        JobHistoryService.JobState completed = 
buildJobState(jobWithFinishTime, 1000L, 2000L);
+
+        when(finishedJobStateMap.values())
+                .thenReturn((java.util.Collection) 
Arrays.asList(missingFinishTime, completed));
+        when(finishedJobMetricsMap.getOrDefault(jobId, 
JobMetrics.empty())).thenReturn(null);
+        when(finishedJobMetricsMap.getOrDefault(jobWithFinishTime, 
JobMetrics.empty()))
+                .thenThrow(new RuntimeException("metrics unavailable"));
+        when(finishedJobVertexInfoMap.get(jobId))
+                .thenThrow(new RuntimeException("dag unavailable"));
+        when(finishedJobVertexInfoMap.get(jobWithFinishTime)).thenReturn(null);
+
+        JsonArray result = jobInfoService.getJobsByStateJson("");
+
+        Assertions.assertEquals(2, result.size());
+        Assertions.assertEquals(
+                jobWithFinishTime.toString(),
+                result.get(0).asObject().getString(RestConstant.JOB_ID, null));
+        Assertions.assertEquals(
+                jobId.toString(), 
result.get(1).asObject().getString(RestConstant.JOB_ID, null));
+    }
 }

Reply via email to