You are viewing a plain text version of this content. The canonical link for it is here.
Posted to common-commits@hadoop.apache.org by sl...@apache.org on 2026/10/06 02:20:28 UTC

(hadoop) branch trunk updated: MAPREDUCE-7547. HsWebApp should bind App request-scoped instead of singleton (#8790)

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

slfan1989 pushed a commit to branch trunk
in repository https://gitbox.apache.org/repos/asf/hadoop.git


The following commit(s) were added to refs/heads/trunk by this push:
     new 14b55ba5be0 MAPREDUCE-7547. HsWebApp should bind App request-scoped instead of singleton (#8790)
14b55ba5be0 is described below

commit 14b55ba5be09a19ca020932769fdd59022bc0002
Author: Jose Luis López López <jo...@gmail.com>
AuthorDate: Tue Oct 6 04:20:22 2026 +0200

    MAPREDUCE-7547. HsWebApp should bind App request-scoped instead of singleton (#8790)
    
    MAPREDUCE-7547. HsWebApp should bind App request-scoped instead of singleton
    
    App carries per-request job/task state from the controller to the view.
    Binding it as a singleton lets concurrent JobHistoryServer requests overwrite each other's state. Use request scope so the controller and view share one App within a request while keeping requests isolated.
    
    Add regression tests for the App binding scope and attempts page rendering through a real HsWebApp. Set connection and read timeouts in the page test and disconnect in a finally block.
    
    Co-authored-by: Claude Opus 5.5 <no...@anthropic.com>
    Co-authored-by: Shilun Fan <sl...@apache.org>
    Signed-off-by: Shilun Fan <sl...@apache.org>
---
 .../hadoop/mapreduce/v2/hs/webapp/HsWebApp.java    |  4 +-
 .../mapreduce/v2/hs/webapp/TestHSWebApp.java       | 86 ++++++++++++++++++++++
 2 files changed, 88 insertions(+), 2 deletions(-)

diff --git a/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-hs/src/main/java/org/apache/hadoop/mapreduce/v2/hs/webapp/HsWebApp.java b/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-hs/src/main/java/org/apache/hadoop/mapreduce/v2/hs/webapp/HsWebApp.java
index 5592d04bd9f..6f5876454a1 100644
--- a/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-hs/src/main/java/org/apache/hadoop/mapreduce/v2/hs/webapp/HsWebApp.java
+++ b/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-hs/src/main/java/org/apache/hadoop/mapreduce/v2/hs/webapp/HsWebApp.java
@@ -31,7 +31,7 @@
 import org.apache.hadoop.mapreduce.v2.hs.HistoryContext;
 import org.apache.hadoop.yarn.webapp.WebApp;
 
-import com.google.inject.Singleton;
+import com.google.inject.servlet.RequestScoped;
 
 public class HsWebApp extends WebApp implements AMParams {
 
@@ -43,7 +43,7 @@ public HsWebApp(HistoryContext history) {
 
   @Override
   public void setup() {
-    bind(App.class).in(Singleton.class);
+    bind(App.class).in(RequestScoped.class);
     bind(AppContext.class).toInstance(history);
     bind(HistoryContext.class).toInstance(history);
     route("/", HsController.class);
diff --git a/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-hs/src/test/java/org/apache/hadoop/mapreduce/v2/hs/webapp/TestHSWebApp.java b/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-hs/src/test/java/org/apache/hadoop/mapreduce/v2/hs/webapp/TestHSWebApp.java
index 4fef7f13965..ad2c7abb579 100644
--- a/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-hs/src/test/java/org/apache/hadoop/mapreduce/v2/hs/webapp/TestHSWebApp.java
+++ b/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-hs/src/test/java/org/apache/hadoop/mapreduce/v2/hs/webapp/TestHSWebApp.java
@@ -28,30 +28,50 @@
 import static org.apache.hadoop.yarn.webapp.YarnWebParams.ENTITY_STRING;
 import static org.apache.hadoop.yarn.webapp.YarnWebParams.NM_NODENAME;
 import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
 import static org.junit.jupiter.api.Assertions.assertTrue;
 import static org.mockito.Mockito.verify;
 
 import java.io.IOException;
+import java.io.InputStream;
 import java.io.PrintWriter;
+import java.net.HttpURLConnection;
+import java.net.URL;
+import java.nio.charset.StandardCharsets;
 import java.util.HashMap;
 import java.util.Map;
 
 import org.apache.hadoop.conf.Configuration;
 import org.apache.hadoop.mapreduce.v2.api.records.JobId;
+import org.apache.hadoop.mapreduce.v2.api.records.TaskType;
 import org.apache.hadoop.mapreduce.v2.app.AppContext;
 import org.apache.hadoop.mapreduce.v2.app.MRApp;
 import org.apache.hadoop.mapreduce.v2.app.MockAppContext;
 import org.apache.hadoop.mapreduce.v2.app.MockJobs;
+import org.apache.hadoop.mapreduce.v2.app.job.Job;
+import org.apache.hadoop.mapreduce.v2.app.job.Task;
+import org.apache.hadoop.mapreduce.v2.app.job.TaskAttempt;
+import org.apache.hadoop.mapreduce.v2.app.webapp.App;
 import org.apache.hadoop.mapreduce.v2.app.webapp.TestAMWebApp;
+import org.apache.hadoop.mapreduce.v2.hs.MockHistoryContext;
+import org.apache.hadoop.mapreduce.v2.util.MRApps.TaskAttemptStateUI;
 import org.apache.hadoop.yarn.api.records.NodeId;
 import org.apache.hadoop.yarn.conf.YarnConfiguration;
 import org.apache.hadoop.yarn.webapp.View;
+import org.apache.hadoop.yarn.webapp.WebApp;
+import org.apache.hadoop.yarn.webapp.WebApps;
 import org.apache.hadoop.yarn.webapp.log.AggregatedLogsPage;
 import org.apache.hadoop.yarn.webapp.test.WebAppTests;
 import org.junit.jupiter.api.Test;
 
 import com.google.inject.AbstractModule;
+import com.google.inject.Binding;
 import com.google.inject.Injector;
+import com.google.inject.Key;
+import com.google.inject.Scopes;
+import com.google.inject.servlet.RequestScoped;
+import com.google.inject.servlet.ServletScopes;
+import com.google.inject.spi.Elements;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -127,6 +147,72 @@ public void testTaskViewNaturalSortType() {
         ctx, params);
   }
   
+  /**
+   * App carries per-request job/task state from the controller to the view,
+   * so it must be shared within a request but never across requests.
+   */
+  @Test
+  public void testAppBindingIsRequestScoped() {
+    HsWebApp webApp = new HsWebApp(new MockHistoryContext(0, 1, 1, 1));
+    Binding<?> appBinding = Elements.getElements(webApp).stream()
+        .filter(e -> e instanceof Binding
+            && ((Binding<?>) e).getKey().equals(Key.get(App.class)))
+        .map(e -> (Binding<?>) e)
+        .findFirst()
+        .orElse(null);
+    assertNotNull(appBinding, "HsWebApp should bind App");
+    assertTrue(Scopes.isScoped(appBinding, ServletScopes.REQUEST,
+        RequestScoped.class), "App should be bound request-scoped");
+  }
+
+  /**
+   * Render the attempts page through a real HsWebApp, so the controller and
+   * the view get App from the webapp's own Guice bindings.
+   */
+  @Test
+  public void testAttemptsPageRender() throws Exception {
+    MockHistoryContext ctx = new MockHistoryContext(0, 1, 2, 2);
+    JobId jobId = ctx.getAllJobs().keySet().iterator().next();
+    Job job = ctx.getJob(jobId);
+    TaskAttempt attempt = null;
+    TaskAttemptStateUI state = null;
+    for (Task task : job.getTasks(TaskType.MAP).values()) {
+      for (TaskAttempt ta : task.getAttempts().values()) {
+        for (TaskAttemptStateUI ui : TaskAttemptStateUI.values()) {
+          if (state == null && ui.correspondsTo(ta.getState())) {
+            attempt = ta;
+            state = ui;
+          }
+        }
+      }
+    }
+    assertNotNull(attempt, "Mock job should have a map attempt to render");
+
+    WebApp webApp = WebApps.$for("jobhistory").at(0)
+        .start(new HsWebApp(ctx));
+    try {
+      URL url = new URL("http://localhost:"
+          + webApp.getListenerAddress().getPort() + "/jobhistory/attempts/"
+          + jobId + "/m/" + state);
+      HttpURLConnection conn = (HttpURLConnection) url.openConnection();
+      conn.setConnectTimeout(10_000);
+      conn.setReadTimeout(10_000);
+      try {
+        assertEquals(HttpURLConnection.HTTP_OK, conn.getResponseCode(),
+            url.toString());
+        try (InputStream in = conn.getInputStream()) {
+          assertTrue(new String(in.readAllBytes(), StandardCharsets.UTF_8)
+              .contains(attempt.getID().toString()),
+              "Attempts page should list " + attempt.getID());
+        }
+      } finally {
+        conn.disconnect();
+      }
+    } finally {
+      webApp.stop();
+    }
+  }
+
   @Test public void testAttemptsView() {
     LOG.info("HsAttemptsPage");
     AppContext appContext = new MockAppContext(0, 1, 1, 1);


---------------------------------------------------------------------
To unsubscribe, e-mail: common-commits-unsubscribe@hadoop.apache.org
For additional commands, e-mail: common-commits-help@hadoop.apache.org