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

markt-asf pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/tomcat.git


The following commit(s) were added to refs/heads/main by this push:
     new ce9f44fdb5 Copilot/review commit f206204 (#997)
ce9f44fdb5 is described below

commit ce9f44fdb564b0fb7c6fccc145cb2d6e301093c5
Author: Mark Thomas <[email protected]>
AuthorDate: Thu Apr 23 19:30:13 2026 +0200

    Copilot/review commit f206204 (#997)
    
    * Fix race condition in ReplicatedContext.getServletContext()
    
    Co-authored-by: copilot-swe-agent[bot] 
<[email protected]>
    Co-authored-by: markt-asf <[email protected]>
---
 .../catalina/ha/context/ReplicatedContext.java     | 10 ++--
 .../catalina/ha/context/TestReplicatedContext.java | 57 ++++++++++++++++++++++
 2 files changed, 64 insertions(+), 3 deletions(-)

diff --git a/java/org/apache/catalina/ha/context/ReplicatedContext.java 
b/java/org/apache/catalina/ha/context/ReplicatedContext.java
index 5ba2583fb0..8db3b70394 100644
--- a/java/org/apache/catalina/ha/context/ReplicatedContext.java
+++ b/java/org/apache/catalina/ha/context/ReplicatedContext.java
@@ -118,9 +118,13 @@ public class ReplicatedContext extends StandardContext 
implements MapOwner {
     @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 3840a19249..dffdab05db 100644
--- a/test/org/apache/catalina/ha/context/TestReplicatedContext.java
+++ b/test/org/apache/catalina/ha/context/TestReplicatedContext.java
@@ -18,7 +18,12 @@ package org.apache.catalina.ha.context;
 
 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 @@ import org.apache.tomcat.util.buf.ByteChunk;
 
 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<Thread> threads = new ArrayList<>();
+        ServletContext[] results = new ServletContext[numThreads];
+        AtomicReference<Throwable> 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();


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to