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

dsmiley pushed a commit to branch branch_10x
in repository https://gitbox.apache.org/repos/asf/solr.git

commit cf6cb4345c6a1d88f3c99d57178790b8bb60df49
Author: David Smiley <[email protected]>
AuthorDate: Tue Apr 14 11:31:46 2026 -0400

    SOLR-18168: Simplify JettySolrRunner (#4262)
    
    * Remove GzipHandler from JettySolrRunner.  Migrate the test to BATS.
    * Refactor JettySolrRunner filter access to use generic getFilter(). 
Broaden getExtraRequestFilters() return type from SortedMap to SequencedMap.
    * Remove Servlet404.
    * Move/Rename DebugFilter to ServletFixtures as DelayFilter
---
 .../org/apache/solr/cloud/ShardRoutingTest.java    |  13 +-
 .../solr/servlet/TestRequestRateLimiter.java       |   5 +-
 .../solr/update/TestInPlaceUpdatesDistrib.java     |  26 ++-
 solr/packaging/test/test_compression.bats          |  47 ++++++
 .../apache/solr/BaseDistributedSearchTestCase.java |   3 +-
 .../org/apache/solr/embedded/JettySolrRunner.java  | 186 ++++-----------------
 .../java/org/apache/solr/util/ServletFixtures.java |  73 ++++++++
 .../solr/cloud/MiniSolrCloudClusterTest.java       |   3 +-
 8 files changed, 191 insertions(+), 165 deletions(-)

diff --git a/solr/core/src/test/org/apache/solr/cloud/ShardRoutingTest.java 
b/solr/core/src/test/org/apache/solr/cloud/ShardRoutingTest.java
index cbb8df7d37f..1d705df29fe 100644
--- a/solr/core/src/test/org/apache/solr/cloud/ShardRoutingTest.java
+++ b/solr/core/src/test/org/apache/solr/cloud/ShardRoutingTest.java
@@ -16,14 +16,18 @@
  */
 package org.apache.solr.cloud;
 
+import jakarta.servlet.Filter;
 import java.lang.invoke.MethodHandles;
+import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.SequencedMap;
 import org.apache.solr.client.solrj.SolrClient;
 import org.apache.solr.client.solrj.request.UpdateRequest;
 import org.apache.solr.client.solrj.response.QueryResponse;
 import org.apache.solr.common.params.ShardParams;
 import org.apache.solr.embedded.JettySolrRunner;
+import org.apache.solr.util.ServletFixtures;
 import org.junit.BeforeClass;
 import org.junit.Test;
 import org.slf4j.Logger;
@@ -327,10 +331,15 @@ public class ShardRoutingTest extends 
AbstractFullDistribZkTestBase {
     }
   }
 
+  @Override
+  public SequencedMap<Class<? extends Filter>, String> 
getExtraRequestFilters() {
+    return new LinkedHashMap<>(Map.of(ServletFixtures.DelayServlet.class, 
"/*"));
+  }
+
   long getNumRequests() {
-    long n = controlJetty.getDebugFilter().getTotalRequests();
+    long n = 
controlJetty.getFilter(ServletFixtures.DelayServlet.class).getTotalRequests();
     for (JettySolrRunner jetty : jettys) {
-      n += jetty.getDebugFilter().getTotalRequests();
+      n += 
jetty.getFilter(ServletFixtures.DelayServlet.class).getTotalRequests();
     }
     return n;
   }
diff --git 
a/solr/core/src/test/org/apache/solr/servlet/TestRequestRateLimiter.java 
b/solr/core/src/test/org/apache/solr/servlet/TestRequestRateLimiter.java
index d551862227c..43f627d917a 100644
--- a/solr/core/src/test/org/apache/solr/servlet/TestRequestRateLimiter.java
+++ b/solr/core/src/test/org/apache/solr/servlet/TestRequestRateLimiter.java
@@ -72,7 +72,8 @@ public class TestRequestRateLimiter extends SolrCloudTestCase 
{
       CollectionAdminRequest.createCollection(FIRST_COLLECTION, 1, 
1).process(client);
       cluster.waitForActiveCollection(FIRST_COLLECTION, 1, 1);
 
-      RateLimitFilter rateLimitFilter = 
cluster.getJettySolrRunner(0).getSolrRateLimitFilter();
+      RateLimitFilter rateLimitFilter =
+          cluster.getJettySolrRunner(0).getFilter(RateLimitFilter.class);
 
       RateLimiterConfig rateLimiterConfig =
           new RateLimiterConfig(
@@ -291,7 +292,7 @@ public class TestRequestRateLimiter extends 
SolrCloudTestCase {
       CollectionAdminRequest.createCollection(SECOND_COLLECTION, 1, 
1).process(client);
       cluster.waitForActiveCollection(SECOND_COLLECTION, 1, 1);
 
-      RateLimitFilter rateLimitFilter = 
cluster.getJettySolrRunner(0).getSolrRateLimitFilter();
+      var rateLimitFilter = 
cluster.getJettySolrRunner(0).getFilter(RateLimitFilter.class);
 
       RateLimiterConfig queryRateLimiterConfig =
           new RateLimiterConfig(
diff --git 
a/solr/core/src/test/org/apache/solr/update/TestInPlaceUpdatesDistrib.java 
b/solr/core/src/test/org/apache/solr/update/TestInPlaceUpdatesDistrib.java
index 8576af70208..011bad9e556 100644
--- a/solr/core/src/test/org/apache/solr/update/TestInPlaceUpdatesDistrib.java
+++ b/solr/core/src/test/org/apache/solr/update/TestInPlaceUpdatesDistrib.java
@@ -19,14 +19,17 @@ package org.apache.solr.update;
 
 import static org.hamcrest.core.StringContains.containsString;
 
+import jakarta.servlet.Filter;
 import java.io.IOException;
 import java.lang.invoke.MethodHandles;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
+import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.Random;
+import java.util.SequencedMap;
 import java.util.concurrent.Callable;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Future;
@@ -62,6 +65,7 @@ import org.apache.solr.embedded.JettySolrRunner;
 import org.apache.solr.index.NoMergePolicyFactory;
 import org.apache.solr.update.processor.DistributedUpdateProcessor;
 import org.apache.solr.util.RefCounted;
+import org.apache.solr.util.ServletFixtures;
 import org.apache.solr.util.TimeOut;
 import org.apache.zookeeper.KeeperException;
 import org.junit.BeforeClass;
@@ -202,9 +206,14 @@ public class TestInPlaceUpdatesDistrib extends 
AbstractFullDistribZkTestBase {
     // reorderedDBQsUsingUpdatedValueFromADroppedUpdate();
   }
 
+  @Override
+  public SequencedMap<Class<? extends Filter>, String> 
getExtraRequestFilters() {
+    return new LinkedHashMap<>(Map.of(ServletFixtures.DelayServlet.class, 
"/*"));
+  }
+
   private void resetDelays() {
     for (JettySolrRunner j : jettys) {
-      j.getDebugFilter().unsetDelay();
+      j.getFilter(ServletFixtures.DelayServlet.class).unsetDelay();
     }
   }
 
@@ -1236,7 +1245,7 @@ public class TestInPlaceUpdatesDistrib extends 
AbstractFullDistribZkTestBase {
         .get(SHARD1)
         .get(1)
         .jetty
-        .getDebugFilter()
+        .getFilter(ServletFixtures.DelayServlet.class)
         .addDelay("Waiting for dependant update to timeout", 1, 6000);
 
     ExecutorService threadpool =
@@ -1324,7 +1333,12 @@ public class TestInPlaceUpdatesDistrib extends 
AbstractFullDistribZkTestBase {
     {
       clearIndex();
       commit();
-      shardToJetty.get(SHARD1).get(1).jetty.getDebugFilter().unsetDelay();
+      shardToJetty
+          .get(SHARD1)
+          .getFirst()
+          .jetty
+          .getFilter(ServletFixtures.DelayServlet.class)
+          .unsetDelay();
 
       updates.add(regularDeleteRequest(1));
 
@@ -1332,13 +1346,13 @@ public class TestInPlaceUpdatesDistrib extends 
AbstractFullDistribZkTestBase {
           .get(SHARD1)
           .get(1)
           .jetty
-          .getDebugFilter()
+          .getFilter(ServletFixtures.DelayServlet.class)
           .addDelay("Waiting for dependant update to timeout", 1, 5999); // 
the first update
       shardToJetty
           .get(SHARD1)
           .get(1)
           .jetty
-          .getDebugFilter()
+          .getFilter(ServletFixtures.DelayServlet.class)
           .addDelay("Waiting for dependant update to timeout", 4, 5998); // 
the delete update
 
       threadpool =
@@ -1635,7 +1649,7 @@ public class TestInPlaceUpdatesDistrib extends 
AbstractFullDistribZkTestBase {
         .get(SHARD1)
         .get(1)
         .jetty
-        .getDebugFilter()
+        .getFilter(ServletFixtures.DelayServlet.class)
         .addDelay("Waiting for dependant update to timeout", 2, 8000);
 
     ExecutorService threadpool =
diff --git a/solr/packaging/test/test_compression.bats 
b/solr/packaging/test/test_compression.bats
new file mode 100644
index 00000000000..575490c8f99
--- /dev/null
+++ b/solr/packaging/test/test_compression.bats
@@ -0,0 +1,47 @@
+#!/usr/bin/env bats
+
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+load bats_helper
+
+setup_file() {
+  common_clean_setup
+  solr start -e films
+}
+
+setup() {
+  common_setup
+}
+
+teardown_file() {
+  save_home_on_failure
+  solr stop --all >/dev/null 2>&1
+}
+
+@test "server does not compress response without Accept-Encoding header" {
+  run curl -s -D - -o /dev/null 
"http://localhost:${SOLR_PORT}/solr/films/select?q=*:*&rows=100";
+  refute_output --partial "Content-Encoding:"
+}
+
+@test "server compresses response when Accept-Encoding: gzip is requested" {
+  run curl -s -D - -o /dev/null -H "Accept-Encoding: gzip" 
"http://localhost:${SOLR_PORT}/solr/films/select?q=*:*&rows=100";
+  assert_output --partial "gzip"
+}
+
+@test "compressed response can be decompressed and parsed" {
+  run curl -s --compressed 
"http://localhost:${SOLR_PORT}/solr/films/select?q=*:*&rows=100";
+  assert_output --partial '"status":0'
+}
diff --git 
a/solr/test-framework/src/java/org/apache/solr/BaseDistributedSearchTestCase.java
 
b/solr/test-framework/src/java/org/apache/solr/BaseDistributedSearchTestCase.java
index 726c736fda7..e72ffaa213c 100644
--- 
a/solr/test-framework/src/java/org/apache/solr/BaseDistributedSearchTestCase.java
+++ 
b/solr/test-framework/src/java/org/apache/solr/BaseDistributedSearchTestCase.java
@@ -37,6 +37,7 @@ import java.util.Map;
 import java.util.Objects;
 import java.util.Properties;
 import java.util.Random;
+import java.util.SequencedMap;
 import java.util.Set;
 import java.util.SortedMap;
 import java.util.concurrent.ConcurrentHashMap;
@@ -439,7 +440,7 @@ public abstract class BaseDistributedSearchTestCase extends 
SolrTestCaseJ4 {
    * Override this method to insert extra filters into the JettySolrRunners 
that are created using
    * createJetty()
    */
-  public SortedMap<Class<? extends Filter>, String> getExtraRequestFilters() {
+  public SequencedMap<Class<? extends Filter>, String> 
getExtraRequestFilters() {
     return null;
   }
 
diff --git 
a/solr/test-framework/src/java/org/apache/solr/embedded/JettySolrRunner.java 
b/solr/test-framework/src/java/org/apache/solr/embedded/JettySolrRunner.java
index 9428b6878c4..f355b057aa4 100644
--- a/solr/test-framework/src/java/org/apache/solr/embedded/JettySolrRunner.java
+++ b/solr/test-framework/src/java/org/apache/solr/embedded/JettySolrRunner.java
@@ -19,16 +19,9 @@ package org.apache.solr.embedded;
 import io.prometheus.metrics.expositionformats.PrometheusTextFormatWriter;
 import jakarta.servlet.DispatcherType;
 import jakarta.servlet.Filter;
-import jakarta.servlet.FilterChain;
-import jakarta.servlet.FilterConfig;
 import jakarta.servlet.ServletContextEvent;
 import jakarta.servlet.ServletException;
-import jakarta.servlet.ServletRequest;
-import jakarta.servlet.ServletResponse;
 import jakarta.servlet.UnavailableException;
-import jakarta.servlet.http.HttpServlet;
-import jakarta.servlet.http.HttpServletRequest;
-import jakarta.servlet.http.HttpServletResponse;
 import java.io.IOException;
 import java.io.PrintStream;
 import java.lang.invoke.MethodHandles;
@@ -37,18 +30,17 @@ import java.net.MalformedURLException;
 import java.net.URI;
 import java.net.URISyntaxException;
 import java.net.URL;
-import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.EnumSet;
 import java.util.List;
 import java.util.Locale;
 import java.util.Map;
+import java.util.NoSuchElementException;
 import java.util.Properties;
 import java.util.Random;
 import java.util.Set;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.TimeoutException;
-import java.util.concurrent.atomic.AtomicInteger;
-import java.util.concurrent.atomic.AtomicLong;
 import org.apache.solr.SolrBackend;
 import org.apache.solr.client.solrj.SolrClient;
 import org.apache.solr.client.solrj.SolrServerException;
@@ -75,6 +67,7 @@ import org.apache.solr.util.TimeOut;
 import org.apache.solr.util.configuration.SSLConfigurationsFactory;
 import org.eclipse.jetty.alpn.server.ALPNServerConnectionFactory;
 import org.eclipse.jetty.ee10.servlet.FilterHolder;
+import org.eclipse.jetty.ee10.servlet.FilterMapping;
 import org.eclipse.jetty.ee10.servlet.ServletContextHandler;
 import org.eclipse.jetty.ee10.servlet.ServletHolder;
 import org.eclipse.jetty.ee10.servlet.Source;
@@ -92,7 +85,6 @@ import org.eclipse.jetty.server.Server;
 import org.eclipse.jetty.server.ServerConnector;
 import org.eclipse.jetty.server.SslConnectionFactory;
 import org.eclipse.jetty.server.handler.GracefulHandler;
-import org.eclipse.jetty.server.handler.gzip.GzipHandler;
 import org.eclipse.jetty.session.DefaultSessionIdManager;
 import org.eclipse.jetty.util.resource.ResourceFactory;
 import org.eclipse.jetty.util.ssl.SslContextFactory;
@@ -117,12 +109,7 @@ public class JettySolrRunner implements SolrBackend {
 
   private Server server;
 
-  volatile FilterHolder debugFilter;
-  volatile FilterHolder requiredFilter;
-  volatile FilterHolder rateLimitFilter;
-  volatile FilterHolder authFilter;
-  volatile ServletHolder solrServlet;
-  private FilterHolder tracingFilter;
+  private volatile ServletHolder solrServlet;
 
   private int jettyPort = -1;
 
@@ -132,8 +119,6 @@ public class JettySolrRunner implements SolrBackend {
 
   private volatile boolean startedBefore = false;
 
-  private List<FilterHolder> extraFilters;
-
   private int proxyPort = -1;
 
   private final boolean enableProxy;
@@ -148,69 +133,6 @@ public class JettySolrRunner implements SolrBackend {
 
   private volatile boolean started = false;
 
-  public static class DebugFilter implements Filter {
-    private static final Logger log = 
LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
-
-    private AtomicLong nRequests = new AtomicLong();
-
-    List<Delay> delays = new ArrayList<>();
-
-    public long getTotalRequests() {
-      return nRequests.get();
-    }
-
-    /**
-     * Introduce a delay of specified milliseconds for the specified request.
-     *
-     * @param reason Info message logged when delay occurs
-     * @param count The count-th request will experience a delay
-     * @param delay There will be a delay of this many milliseconds
-     */
-    public void addDelay(String reason, int count, int delay) {
-      delays.add(new Delay(reason, count, delay));
-    }
-
-    /** Remove any delay introduced before. */
-    public void unsetDelay() {
-      delays.clear();
-    }
-
-    @Override
-    public void init(FilterConfig filterConfig) throws ServletException {}
-
-    @Override
-    public void doFilter(
-        ServletRequest servletRequest, ServletResponse servletResponse, 
FilterChain filterChain)
-        throws IOException, ServletException {
-      nRequests.incrementAndGet();
-      executeDelay();
-      filterChain.doFilter(servletRequest, servletResponse);
-    }
-
-    @Override
-    public void destroy() {}
-
-    private void executeDelay() {
-      int delayMs = 0;
-      for (Delay delay : delays) {
-        log.info("Delaying {}, for reason: {}", delay.delayValue, 
delay.reason);
-        if (delay.counter.decrementAndGet() == 0) {
-          delayMs += delay.delayValue;
-        }
-      }
-
-      if (delayMs > 0) {
-        log.info("Pausing this socket connection for {}ms...", delayMs);
-        try {
-          Thread.sleep(delayMs);
-        } catch (InterruptedException e) {
-          throw new RuntimeException(e);
-        }
-        log.info("Waking up after the delay of {}ms...", delayMs);
-      }
-    }
-  }
-
   /**
    * Create a new JettySolrRunner.
    *
@@ -372,7 +294,7 @@ public class JettySolrRunner implements SolrBackend {
     {
       // Initialize the servlets
       final ServletContextHandler root =
-          new ServletContextHandler("/solr", ServletContextHandler.SESSIONS);
+          new ServletContextHandler("/solr", 
ServletContextHandler.NO_SESSIONS);
       root.setServer(server);
       root.setBaseResource(ResourceFactory.of(server).newResource("."));
       root.addEventListener(
@@ -398,11 +320,8 @@ public class JettySolrRunner implements SolrBackend {
             }
           });
 
-      debugFilter = root.addFilter(DebugFilter.class, "/*", 
EnumSet.of(DispatcherType.REQUEST));
-      extraFilters = new ArrayList<>();
       for (Map.Entry<Class<? extends Filter>, String> entry : 
config.extraFilters.entrySet()) {
-        extraFilters.add(
-            root.addFilter(entry.getKey(), entry.getValue(), 
EnumSet.of(DispatcherType.REQUEST)));
+        root.addFilter(entry.getKey(), entry.getValue(), 
EnumSet.of(DispatcherType.REQUEST));
       }
 
       for (Map.Entry<ServletHolder, String> entry : 
config.extraServlets.entrySet()) {
@@ -411,43 +330,29 @@ public class JettySolrRunner implements SolrBackend {
       // TODO: This needs to be driven by a parsing of web.xml eventually
       //  though we still want to avoid classpath scanning.
 
-      // required request setup
-      requiredFilter = 
root.getServletHandler().newFilterHolder(Source.EMBEDDED);
-      requiredFilter.setHeldClass(RequiredSolrRequestFilter.class);
-
-      // Ratelimit Requests
-      rateLimitFilter = 
root.getServletHandler().newFilterHolder(Source.EMBEDDED);
-      rateLimitFilter.setHeldClass(RateLimitFilter.class);
-
-      // Trace Requests
-      tracingFilter = 
root.getServletHandler().newFilterHolder(Source.EMBEDDED);
-      tracingFilter.setHeldClass(TracingFilter.class);
-
-      // Authenticate Requests
-      authFilter = root.getServletHandler().newFilterHolder(Source.EMBEDDED);
-      authFilter.setHeldClass(AuthenticationFilter.class);
-
-      // Map filters in same path as in web.xml
-      root.addFilter(requiredFilter, "/*", EnumSet.of(DispatcherType.REQUEST));
-      root.addFilter(rateLimitFilter, "/*", 
EnumSet.of(DispatcherType.REQUEST));
-      root.addFilter(tracingFilter, "/*", EnumSet.of(DispatcherType.REQUEST));
-      root.addFilter(authFilter, "/*", EnumSet.of(DispatcherType.REQUEST));
-
       // This is our main workhorse - now a servlet instead of filter
       solrServlet = root.getServletHandler().newServletHolder(Source.EMBEDDED);
+      solrServlet.setName("SolrServlet");
       solrServlet.setHeldClass(SolrServlet.class);
       root.addServlet(solrServlet, "/*");
 
-      // Default servlet as a fall-through
-      ServletHolder defaultHolder = 
root.getServletHandler().newServletHolder(Source.EMBEDDED);
-
-      // considered adding DefaultServlet.class here but perhaps that might 
grant our unit tests
-      // the power to serve static resources on the build machines? Not sure, 
so I'll just give a
-      // name to our existing hack. The tests passed without this, but it will 
ensure that if anyone
-      // ever hits the PathExcludeFilter in the unit test they get a 404 as 
before not a 500
-      defaultHolder.setHeldClass(Servlet404.class);
-      defaultHolder.setName("default");
-      root.addServlet(defaultHolder, "/");
+      // Map filters to SolrServlet by name (same order as web.xml)
+      for (var filterClass :
+          List.<Class<? extends Filter>>of(
+              RequiredSolrRequestFilter.class,
+              RateLimitFilter.class,
+              TracingFilter.class,
+              AuthenticationFilter.class)) {
+        FilterHolder fh = 
root.getServletHandler().newFilterHolder(Source.EMBEDDED);
+        fh.setName(filterClass.getSimpleName());
+        fh.setHeldClass(filterClass);
+        root.getServletHandler().addFilter(fh);
+        FilterMapping fm = new FilterMapping();
+        fm.setFilterName(fh.getName());
+        fm.setServletNames(new String[] {"SolrServlet"});
+        fm.setDispatcherTypes(EnumSet.of(DispatcherType.REQUEST));
+        root.getServletHandler().addFilterMapping(fm);
+      }
 
       // TODO: end area that should be driven by web.xml and webdefault.xml
       chain = root;
@@ -463,13 +368,7 @@ public class JettySolrRunner implements SolrBackend {
       chain = rwh;
     }
 
-    GzipHandler gzipHandler = new GzipHandler();
-    gzipHandler.setHandler(chain);
-
-    gzipHandler.setMinGzipSize(23); // 
https://github.com/eclipse/jetty.project/issues/4191
-    gzipHandler.setIncludedMethods("GET");
-
-    server.setHandler(gzipHandler);
+    server.setHandler(chain);
 
     // Mimic "graceful.mod"
     GracefulHandler graceful = new GracefulHandler();
@@ -486,10 +385,15 @@ public class JettySolrRunner implements SolrBackend {
   }
 
   /**
-   * @return the {@link RateLimitFilter} for this node
+   * @return the first filter implemented by the specified class, or throws an 
exception
    */
-  public RateLimitFilter getSolrRateLimitFilter() {
-    return (RateLimitFilter) rateLimitFilter.getFilter();
+  public <T extends Filter> T getFilter(Class<T> filterClass) {
+    return Arrays.stream(solrServlet.getServletHandler().getFilters())
+        .filter(fh -> fh.getHeldClass() == filterClass)
+        .map(fh -> filterClass.cast(fh.getFilter()))
+        .findFirst()
+        .orElseThrow(
+            () -> new NoSuchElementException("No filter of class: " + 
filterClass.getName()));
   }
 
   @Override
@@ -857,21 +761,9 @@ public class JettySolrRunner implements SolrBackend {
         .build();
   }
 
-  public DebugFilter getDebugFilter() {
-    return (DebugFilter) debugFilter.getFilter();
-  }
-
   // --------------------------------------------------------------
   // --------------------------------------------------------------
 
-  /** This is a stupid hack to give jetty something to attach to */
-  public static class Servlet404 extends HttpServlet {
-    @Override
-    public void service(HttpServletRequest req, HttpServletResponse res) 
throws IOException {
-      res.sendError(404, "Can not find: " + req.getRequestURI());
-    }
-  }
-
   /** A main class that starts jetty+solr This is useful for debugging */
   public static void main(String[] args) throws Exception {
     JettySolrRunner jetty = new JettySolrRunner(".", 8983);
@@ -900,18 +792,6 @@ public class JettySolrRunner implements SolrBackend {
     cores.waitForLoadingCoresToFinish(timeoutMs);
   }
 
-  static class Delay {
-    final AtomicInteger counter;
-    final int delayValue;
-    final String reason;
-
-    public Delay(String reason, int counter, int delay) {
-      this.reason = reason;
-      this.counter = new AtomicInteger(counter);
-      this.delayValue = delay;
-    }
-  }
-
   public SocketProxy getProxy() {
     return proxy;
   }
diff --git 
a/solr/test-framework/src/java/org/apache/solr/util/ServletFixtures.java 
b/solr/test-framework/src/java/org/apache/solr/util/ServletFixtures.java
index b3551fa8a90..cb1b66f6eee 100644
--- a/solr/test-framework/src/java/org/apache/solr/util/ServletFixtures.java
+++ b/solr/test-framework/src/java/org/apache/solr/util/ServletFixtures.java
@@ -17,12 +17,17 @@
 
 package org.apache.solr.util;
 
+import jakarta.servlet.Filter;
+import jakarta.servlet.FilterChain;
 import jakarta.servlet.ServletException;
+import jakarta.servlet.ServletRequest;
+import jakarta.servlet.ServletResponse;
 import jakarta.servlet.http.Cookie;
 import jakarta.servlet.http.HttpServlet;
 import jakarta.servlet.http.HttpServletRequest;
 import jakarta.servlet.http.HttpServletResponse;
 import java.io.IOException;
+import java.lang.invoke.MethodHandles;
 import java.nio.charset.StandardCharsets;
 import java.util.ArrayList;
 import java.util.Enumeration;
@@ -31,13 +36,81 @@ import java.util.List;
 import java.util.Locale;
 import java.util.Map;
 import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicLong;
 import java.util.stream.IntStream;
 import org.apache.solr.common.util.SuppressForbidden;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 public class ServletFixtures {
 
   private ServletFixtures() {}
 
+  /** A Servlet {@link Filter} that adds delays. */
+  public static class DelayServlet implements Filter {
+    private static final Logger log = 
LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
+
+    private AtomicLong nRequests = new AtomicLong();
+
+    List<Delay> delays = new ArrayList<>();
+
+    public long getTotalRequests() {
+      return nRequests.get();
+    }
+
+    /**
+     * Introduce a delay of specified milliseconds for the specified request.
+     *
+     * @param reason Info message logged when delay occurs
+     * @param count The count-th request will experience a delay
+     * @param delayMs There will be a delay of this many milliseconds
+     */
+    public void addDelay(String reason, int count, int delayMs) {
+      delays.add(new Delay(reason, count, delayMs));
+    }
+
+    /** Remove any delay introduced before. */
+    public void unsetDelay() {
+      delays.clear();
+    }
+
+    @Override
+    public void doFilter(
+        ServletRequest servletRequest, ServletResponse servletResponse, 
FilterChain filterChain)
+        throws IOException, ServletException {
+      nRequests.incrementAndGet();
+      executeDelay();
+      filterChain.doFilter(servletRequest, servletResponse);
+    }
+
+    private void executeDelay() {
+      int delayMs = 0;
+      for (Delay delay : delays) {
+        log.info("Delaying {}, for reason: {}", delay.delayMsValue, 
delay.reason);
+        if (delay.counter.decrementAndGet() == 0) {
+          delayMs += delay.delayMsValue;
+        }
+      }
+
+      if (delayMs > 0) {
+        log.info("Pausing this socket connection for {}ms...", delayMs);
+        try {
+          Thread.sleep(delayMs);
+        } catch (InterruptedException e) {
+          throw new RuntimeException(e);
+        }
+        log.info("Waking up after the delay of {}ms...", delayMs);
+      }
+    }
+
+    record Delay(String reason, AtomicInteger counter, int delayMsValue) {
+      Delay(String reason, int counter, int delayMsValue) {
+        this(reason, new AtomicInteger(counter), delayMsValue);
+      }
+    }
+  }
+
   public static class RedirectServlet extends HttpServlet {
     @Override
     protected void doGet(HttpServletRequest req, HttpServletResponse resp)
diff --git 
a/solr/test-framework/src/test/org/apache/solr/cloud/MiniSolrCloudClusterTest.java
 
b/solr/test-framework/src/test/org/apache/solr/cloud/MiniSolrCloudClusterTest.java
index 605e56bf5a9..c5f5bb51043 100644
--- 
a/solr/test-framework/src/test/org/apache/solr/cloud/MiniSolrCloudClusterTest.java
+++ 
b/solr/test-framework/src/test/org/apache/solr/cloud/MiniSolrCloudClusterTest.java
@@ -32,6 +32,7 @@ import org.apache.solr.core.SolrCore;
 import org.apache.solr.embedded.JettyConfig;
 import org.apache.solr.embedded.JettySolrRunner;
 import org.apache.solr.util.RevertDefaultThreadHandlerRule;
+import org.apache.solr.util.ServletFixtures;
 import org.junit.ClassRule;
 import org.junit.Test;
 import org.junit.rules.RuleChain;
@@ -103,7 +104,7 @@ public class MiniSolrCloudClusterTest extends 
SolrTestCaseJ4 {
   public void testExtraFilters() throws Exception {
     JettyConfig.Builder jettyConfig = JettyConfig.builder();
     jettyConfig.waitForLoadingCoresToFinish(null);
-    jettyConfig.withFilter(JettySolrRunner.DebugFilter.class, "*");
+    jettyConfig.withFilter(ServletFixtures.DelayServlet.class, "*");
     MiniSolrCloudCluster cluster =
         new MiniSolrCloudCluster(random().nextInt(3) + 1, createTempDir(), 
jettyConfig.build());
     cluster.shutdown();

Reply via email to