From 7f675d92313b2287aa4e6ef1ec3ce1ed67aff8d3 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 23 Apr 2026 17:23:41 +0000 Subject: [PATCH 1/2] Fix race condition in ReplicatedContext.getServletContext() Agent-Logs-Url: https://github.com/markt-asf/tomcat/sessions/14161c12-b181-4367-a8d5-877b7e26f95e Co-authored-by: markt-asf <4690029+markt-asf@users.noreply.github.com> --- .../ha/context/ReplicatedContext.java | 10 +++- .../ha/context/TestReplicatedContext.java | 57 +++++++++++++++++++ webapps/docs/changelog.xml | 5 ++ 3 files changed, 69 insertions(+), 3 deletions(-) diff --git a/java/org/apache/catalina/ha/context/ReplicatedContext.java b/java/org/apache/catalina/ha/context/ReplicatedContext.java index 5ba2583fb0a0..8db3b70394cf 100644 --- a/java/org/apache/catalina/ha/context/ReplicatedContext.java +++ b/java/org/apache/catalina/ha/context/ReplicatedContext.java @@ -118,9 +118,13 @@ public ClassLoader[] getClassLoaders() { @Override public ServletContext getServletContext() { if (context == null) { - context = new ReplApplContext(this); - if (getAltDDName() != null) { - context.setAttribute(Globals.ALT_DD_ATTR, getAltDDName()); + synchronized (this) { + if (context == null) { + context = new ReplApplContext(this); + if (getAltDDName() != null) { + context.setAttribute(Globals.ALT_DD_ATTR, getAltDDName()); + } + } } } diff --git a/test/org/apache/catalina/ha/context/TestReplicatedContext.java b/test/org/apache/catalina/ha/context/TestReplicatedContext.java index 3840a19249cd..dffdab05dbf5 100644 --- a/test/org/apache/catalina/ha/context/TestReplicatedContext.java +++ b/test/org/apache/catalina/ha/context/TestReplicatedContext.java @@ -18,7 +18,12 @@ import java.io.File; import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.CyclicBarrier; +import java.util.concurrent.atomic.AtomicReference; +import jakarta.servlet.ServletContext; import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServlet; import jakarta.servlet.http.HttpServletRequest; @@ -37,6 +42,58 @@ public class TestReplicatedContext extends TomcatBaseTest { + @Test + public void testGetServletContextReturnsSameInstanceUnderConcurrency() throws Exception { + Tomcat tomcat = getTomcatInstance(); + Host host = tomcat.getHost(); + if (host instanceof StandardHost) { + ((StandardHost) host).setContextClass(ReplicatedContext.class.getName()); + } + + File root = new File("test/webapp"); + ReplicatedContext replicatedContext = + (ReplicatedContext) tomcat.addWebapp(host, "", root.getAbsolutePath()); + tomcat.start(); + + // Null the context field to simulate the window during reload + replicatedContext.context = null; + + int numThreads = 20; + CyclicBarrier barrier = new CyclicBarrier(numThreads); + List threads = new ArrayList<>(); + ServletContext[] results = new ServletContext[numThreads]; + AtomicReference failure = new AtomicReference<>(); + + for (int i = 0; i < numThreads; i++) { + final int index = i; + Thread thread = new Thread(() -> { + try { + barrier.await(); + results[index] = replicatedContext.getServletContext(); + } catch (Throwable ex) { + failure.set(ex); + } + }); + thread.start(); + threads.add(thread); + } + + for (Thread thread : threads) { + thread.join(5000); + } + + if (failure.get() != null) { + Assert.fail("Thread failed: " + failure.get()); + } + + ServletContext first = results[0]; + Assert.assertNotNull(first); + for (int i = 1; i < numThreads; i++) { + Assert.assertSame(first, results[i]); + } + } + + @Test public void testBug57425() throws LifecycleException, IOException { Tomcat tomcat = getTomcatInstance(); diff --git a/webapps/docs/changelog.xml b/webapps/docs/changelog.xml index 34f4bfd66f2b..8ef5bfae68dc 100644 --- a/webapps/docs/changelog.xml +++ b/webapps/docs/changelog.xml @@ -224,6 +224,11 @@ ensure only one ApplicationContext instance is created. (dsoumis) + + Fix the same race condition in + ReplicatedContext.getServletContext() that was fixed in + StandardContext.getServletContext(). (dsoumis) + From a403b7a84214725e1bbfdb12b16816d86766ec79 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 23 Apr 2026 17:24:34 +0000 Subject: [PATCH 2/2] Remove changelog entry for ReplicatedContext fix Agent-Logs-Url: https://github.com/markt-asf/tomcat/sessions/14161c12-b181-4367-a8d5-877b7e26f95e Co-authored-by: markt-asf <4690029+markt-asf@users.noreply.github.com> --- webapps/docs/changelog.xml | 5 ----- 1 file changed, 5 deletions(-) diff --git a/webapps/docs/changelog.xml b/webapps/docs/changelog.xml index 8ef5bfae68dc..34f4bfd66f2b 100644 --- a/webapps/docs/changelog.xml +++ b/webapps/docs/changelog.xml @@ -224,11 +224,6 @@ ensure only one ApplicationContext instance is created. (dsoumis) - - Fix the same race condition in - ReplicatedContext.getServletContext() that was fixed in - StandardContext.getServletContext(). (dsoumis) -