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

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


The following commit(s) were added to refs/heads/master by this push:
     new d5c4ba054b7 [FLINK-40530][runtime-web] Isolate per-entry failures in 
job overview aggregation (#29062)
d5c4ba054b7 is described below

commit d5c4ba054b7e90465aa5229826385a67ad9c51c5
Author: Archit Goyal <[email protected]>
AuthorDate: Thu Sep 10 03:20:59 2026 -0700

    [FLINK-40530][runtime-web] Isolate per-entry failures in job overview 
aggregation (#29062)
    
    A single malformed archive no longer aborts the whole /jobs/overview 
update; it is skipped and logged, and a good overview is kept if all entries 
fail.
    
    Co-authored-by: argoyal2212 <[email protected]>
    Generated-by: GitHub Copilot CLI
---
 .../history/HistoryServerArchiveFetcher.java       | 45 +++++++++++++++-----
 .../history/HistoryServerArchiveFetcherTest.java   | 48 ++++++++++++++++++++++
 2 files changed, 82 insertions(+), 11 deletions(-)

diff --git 
a/flink-runtime-web/src/main/java/org/apache/flink/runtime/webmonitor/history/HistoryServerArchiveFetcher.java
 
b/flink-runtime-web/src/main/java/org/apache/flink/runtime/webmonitor/history/HistoryServerArchiveFetcher.java
index 632551c8eb3..2c17b25cbeb 100644
--- 
a/flink-runtime-web/src/main/java/org/apache/flink/runtime/webmonitor/history/HistoryServerArchiveFetcher.java
+++ 
b/flink-runtime-web/src/main/java/org/apache/flink/runtime/webmonitor/history/HistoryServerArchiveFetcher.java
@@ -505,19 +505,42 @@ public class HistoryServerArchiveFetcher<Entry> 
implements AutoCloseable {
         try {
             Collection<JobDetails> allJobs = new ArrayList<>();
             List<Entry> overviews = 
archiveStorage.getEntriesByPrefix(JOB_OVERVIEWS_KEY_PREFIX);
+            int skipped = 0;
             for (Entry overview : overviews) {
-                MultipleJobsDetails subJobs;
-                // We treated File as a special case, mainly as a performance 
trade-off to avoid the
-                // overhead of loading the archive into string.
-                if (overview instanceof File) {
-                    subJobs = mapper.readValue((File) overview, 
MultipleJobsDetails.class);
-                } else {
-                    subJobs =
-                            mapper.readValue(
-                                    
archiveStorage.readArchiveContent(overview),
-                                    MultipleJobsDetails.class);
+                try {
+                    MultipleJobsDetails subJobs;
+                    // We treated File as a special case, mainly as a 
performance trade-off to
+                    // avoid the overhead of loading the archive into string.
+                    if (overview instanceof File) {
+                        subJobs = mapper.readValue((File) overview, 
MultipleJobsDetails.class);
+                    } else {
+                        subJobs =
+                                mapper.readValue(
+                                        
archiveStorage.readArchiveContent(overview),
+                                        MultipleJobsDetails.class);
+                    }
+                    allJobs.addAll(subJobs.getJobs());
+                } catch (Exception e) {
+                    // A single malformed/incompatible archive (e.g. written 
by a different Flink
+                    // version) must not prevent the remaining archives from 
being aggregated.
+                    skipped++;
+                    LOG.warn(
+                            "Failed to parse job overview from entry {}, 
skipping it.",
+                            overview,
+                            e);
                 }
-                allJobs.addAll(subJobs.getJobs());
+            }
+            if (skipped > 0) {
+                LOG.warn("Skipped {} job overview(s) that could not be 
parsed.", skipped);
+            }
+            if (!overviews.isEmpty() && allJobs.isEmpty()) {
+                // Every entry failed to parse; keep the previously written 
overview instead of
+                // replacing it with an empty one.
+                LOG.error(
+                        "All {} job overview(s) failed to parse; keeping the 
last known good "
+                                + "combined overview instead of overwriting 
it.",
+                        overviews.size());
+                return;
             }
             String overviewWithJobs = mapper.writeValueAsString(new 
MultipleJobsDetails(allJobs));
             archiveStorage.putArchiveContent(
diff --git 
a/flink-runtime-web/src/test/java/org/apache/flink/runtime/webmonitor/history/HistoryServerArchiveFetcherTest.java
 
b/flink-runtime-web/src/test/java/org/apache/flink/runtime/webmonitor/history/HistoryServerArchiveFetcherTest.java
index b95449337ec..877f419a588 100644
--- 
a/flink-runtime-web/src/test/java/org/apache/flink/runtime/webmonitor/history/HistoryServerArchiveFetcherTest.java
+++ 
b/flink-runtime-web/src/test/java/org/apache/flink/runtime/webmonitor/history/HistoryServerArchiveFetcherTest.java
@@ -346,6 +346,54 @@ class HistoryServerArchiveFetcherTest {
         
assertThat(overview.getJobs().iterator().next().getJobId()).isEqualTo(job2);
     }
 
+    @TestTemplate
+    void testUpdateJobOverviewSkipsMalformedEntryInsteadOfFailingEverything() 
throws Exception {
+        JobID goodJob = JobID.generate();
+        createJobArchive(remoteArchiveRootPath, goodJob, true);
+
+        HistoryServerArchiveFetcher<?> fetcher =
+                createArchiveFetcher(remoteArchiveRootPath, true, 
archiveStorage);
+        fetcher.fetchArchives(EAGER);
+
+        // inject a malformed per-job overview entry alongside the good one
+        archiveStorage.putArchiveContent("overviews/malformed-job.json", "{not 
valid json");
+
+        fetcher.updateJobOverview();
+
+        Object overviewObject = archiveStorage.getEntry("jobs/overview.json");
+        String overviewContent = 
archiveStorage.readArchiveContent(overviewObject);
+        MultipleJobsDetails overview =
+                OBJECT_MAPPER.readValue(overviewContent, 
MultipleJobsDetails.class);
+
+        assertThat(overview.getJobs()).hasSize(1);
+        
assertThat(overview.getJobs().iterator().next().getJobId()).isEqualTo(goodJob);
+    }
+
+    @TestTemplate
+    void 
testUpdateJobOverviewDoesNotWipeGoodOverviewWhenAllEntriesAreMalformed() throws 
Exception {
+        JobID job = JobID.generate();
+        createJobArchive(remoteArchiveRootPath, job, true);
+
+        HistoryServerArchiveFetcher<?> fetcher =
+                createArchiveFetcher(remoteArchiveRootPath, true, 
archiveStorage);
+        fetcher.fetchArchives(EAGER);
+
+        Object overviewObjectBefore = 
archiveStorage.getEntry("jobs/overview.json");
+        String overviewContentBefore = 
archiveStorage.readArchiveContent(overviewObjectBefore);
+
+        // corrupt the only per-job overview entry, simulating e.g. an 
incompatible archive
+        // written by a different Flink version
+        archiveStorage.putArchiveContent("overviews/" + job + ".json", "{not 
valid json anymore");
+
+        fetcher.updateJobOverview();
+
+        // the previously written combined overview must be preserved, not 
replaced by an empty
+        // one
+        Object overviewObjectAfter = 
archiveStorage.getEntry("jobs/overview.json");
+        String overviewContentAfter = 
archiveStorage.readArchiveContent(overviewObjectAfter);
+        assertThat(overviewContentAfter).isEqualTo(overviewContentBefore);
+    }
+
     @TestTemplate
     void testLegacyJobOverviewMigration() throws Exception {
         JobID jobId = createLegacyArchive(remoteArchiveRootPath.toPath(), 
false);

Reply via email to