You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sling.apache.org by ro...@apache.org on 2025/01/22 12:48:09 UTC

(sling-org-apache-sling-connection-timeout-agent) branch master updated: SLING-12617 - Internal error when running the connection timeout agent with Java 17 or 21 (#10)

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

rombert pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-connection-timeout-agent.git


The following commit(s) were added to refs/heads/master by this push:
     new 10a6bd0  SLING-12617 - Internal error when running the connection timeout agent with Java 17 or 21 (#10)
10a6bd0 is described below

commit 10a6bd027e9d7addffa79f3486417664544befdf
Author: Robert Munteanu <ro...@apache.org>
AuthorDate: Wed Jan 22 13:48:04 2025 +0100

    SLING-12617 - Internal error when running the connection timeout agent with Java 17 or 21 (#10)
    
    The first step is to get a working CI build:
    - restrict build to Java 11 only
    - update Pax-Exam to access Maven Central over HTTPs
    - update bundles to satisfy new Pax-Exam requirements
    - stop using Maven Central to simulate connection timeouts, it fails on Jenkins.
    - chore: replace 'tt' with 'code' in Javadoc
    - use loopback addresses explicitly and avoid hostnames
    - Skip the connect timeout tests on Windows
    - adjust OkHttp timeout message so it captures the Windows Java 11 output.
---
 .sling-module.json                                 |   5 +
 pom.xml                                            |   6 +-
 .../org/apache/sling/cta/impl/AgentInfoMBean.java  |   2 +-
 .../cta/impl/HttpClient3TimeoutTransformer.java    |   4 +-
 .../cta/impl/HttpClient4TimeoutTransformer.java    |   4 +-
 .../sling/cta/impl/JavaNetTimeoutTransformer.java  |   6 +-
 src/main/java/org/apache/sling/cta/impl/Log.java   |   8 +-
 .../sling/cta/impl/OkHttpTimeoutTransformer.java   |   4 +-
 .../java/org/apache/sling/cta/impl/AgentIT.java    |  31 +++---
 ...erverExtension.java => DelayingHttpServer.java} |  68 +++---------
 .../org/apache/sling/cta/impl/ErrorDescriptor.java |   6 +-
 .../sling/cta/impl/MisbehavingServerControl.java   |   7 ++
 .../sling/cta/impl/MisbehavingServerExtension.java | 116 +++++++++++++--------
 .../java/org/apache/sling/cta/impl/OsgiIT.java     |  33 +++++-
 14 files changed, 167 insertions(+), 133 deletions(-)

diff --git a/.sling-module.json b/.sling-module.json
new file mode 100644
index 0000000..3c6d326
--- /dev/null
+++ b/.sling-module.json
@@ -0,0 +1,5 @@
+{
+  "jenkins": {
+    "jdks": [ 11 ]
+  }
+}
diff --git a/pom.xml b/pom.xml
index 77fdaf8..c4431e5 100644
--- a/pom.xml
+++ b/pom.xml
@@ -173,7 +173,7 @@
         <dependency>
             <groupId>org.apache.felix</groupId>
             <artifactId>org.apache.felix.framework</artifactId>
-            <version>6.0.3</version>
+            <version>7.0.5</version>
             <scope>test</scope>
         </dependency>
         <!-- jacoco agent is manually added to the tests -->
@@ -186,6 +186,6 @@
         </dependency>
     </dependencies>
     <properties>
-        <pax-exam.version>4.13.0</pax-exam.version>
+        <pax-exam.version>4.14.0</pax-exam.version>
     </properties>
-</project>
\ No newline at end of file
+</project>
diff --git a/src/main/java/org/apache/sling/cta/impl/AgentInfoMBean.java b/src/main/java/org/apache/sling/cta/impl/AgentInfoMBean.java
index 294c012..5e40544 100644
--- a/src/main/java/org/apache/sling/cta/impl/AgentInfoMBean.java
+++ b/src/main/java/org/apache/sling/cta/impl/AgentInfoMBean.java
@@ -17,7 +17,7 @@
 package org.apache.sling.cta.impl;
 
 /**
- * Exposes runtime information about the agent using <tt>JMX</tt>.
+ * Exposes runtime information about the agent using <code>JMX</code>.
  *
  */
 public interface AgentInfoMBean {
diff --git a/src/main/java/org/apache/sling/cta/impl/HttpClient3TimeoutTransformer.java b/src/main/java/org/apache/sling/cta/impl/HttpClient3TimeoutTransformer.java
index c1486e6..32e6add 100644
--- a/src/main/java/org/apache/sling/cta/impl/HttpClient3TimeoutTransformer.java
+++ b/src/main/java/org/apache/sling/cta/impl/HttpClient3TimeoutTransformer.java
@@ -25,8 +25,8 @@ import javassist.bytecode.Descriptor;
 /**
  * Sets timeouts for HTTP calls done using <em>Apache Commons HttpClient 3.x</em>
  * 
- * <p>It inserts two calls in <tt>org.apache.commons.httpclient.params.DefaultHttpParamsFactory.createParams</tt> that set
- * default values for <tt>http.connection.timeout</tt> and <tt>http.socket.timeout</tt>.</p>
+ * <p>It inserts two calls in <code>org.apache.commons.httpclient.params.DefaultHttpParamsFactory.createParams</code> that set
+ * default values for <code>http.connection.timeout</code> and <code>http.socket.timeout</code>.</p>
  */
 public class HttpClient3TimeoutTransformer extends MBeanAwareTimeoutTransformer {
     
diff --git a/src/main/java/org/apache/sling/cta/impl/HttpClient4TimeoutTransformer.java b/src/main/java/org/apache/sling/cta/impl/HttpClient4TimeoutTransformer.java
index 8ab7d8e..d67db34 100644
--- a/src/main/java/org/apache/sling/cta/impl/HttpClient4TimeoutTransformer.java
+++ b/src/main/java/org/apache/sling/cta/impl/HttpClient4TimeoutTransformer.java
@@ -21,8 +21,8 @@ import javassist.bytecode.Descriptor;
 /**
  * Sets timeouts for HTTP calls done using <em>Apache HttpComponents Client 4.x</em>
  * 
- * <p>It inserts two calls to <tt>org.apache.http.client.config.RequestConfig$Builder</tt> that set default
- * values for <tt>connectTimeout</tt> and <tt>socketTimeout</tt>.</p>
+ * <p>It inserts two calls to <code>org.apache.http.client.config.RequestConfig$Builder</code> that set default
+ * values for <code>connectTimeout</code> and <code>socketTimeout</code>.</p>
  */
 public class HttpClient4TimeoutTransformer extends UpdateFieldsInConstructorTimeoutTransformer {
 
diff --git a/src/main/java/org/apache/sling/cta/impl/JavaNetTimeoutTransformer.java b/src/main/java/org/apache/sling/cta/impl/JavaNetTimeoutTransformer.java
index 302c400..5dceaba 100644
--- a/src/main/java/org/apache/sling/cta/impl/JavaNetTimeoutTransformer.java
+++ b/src/main/java/org/apache/sling/cta/impl/JavaNetTimeoutTransformer.java
@@ -25,10 +25,10 @@ import javassist.CtMethod;
 import javassist.bytecode.Descriptor;
 
 /**
- * Sets timeouts for HTTP calls done using <tt>java.net.URL</tt>/<tt>java.net.URLConnection</tt>.
+ * Sets timeouts for HTTP calls done using <code>java.net.URL</code>/<code>java.net.URLConnection</code>.
  * 
- * <p>It transforms calls to <tt>connect</tt> methods of internal URL connection classes to set the
- * connect and read timeout in case they have the default value of <tt>0</tt>.</p>
+ * <p>It transforms calls to <code>connect</code> methods of internal URL connection classes to set the
+ * connect and read timeout in case they have the default value of <code>0</code>.</p>
  * 
  * @see URLConnection#getConnectTimeout()
  * @see URLConnection#getReadTimeout()
diff --git a/src/main/java/org/apache/sling/cta/impl/Log.java b/src/main/java/org/apache/sling/cta/impl/Log.java
index 8826461..aa8df74 100644
--- a/src/main/java/org/apache/sling/cta/impl/Log.java
+++ b/src/main/java/org/apache/sling/cta/impl/Log.java
@@ -37,7 +37,7 @@ abstract class Log {
     /**
      * Configures the global logger instance
      * 
-     * @param spec the logger spec, <tt>v</tt> for a console log, anything else for a no-op log
+     * @param spec the logger spec, <code>v</code> for a console log, anything else for a no-op log
      */
     public static void configure(String spec) {
 
@@ -70,7 +70,7 @@ abstract class Log {
      * 
      *  </p>
      *  
-     *  <p>The line separator <tt>%n</tt> is automatically appended to the message.</p>
+     *  <p>The line separator <code>%n</code> is automatically appended to the message.</p>
      * 
      * @param msg the message
      * @param args the arguments
@@ -80,9 +80,9 @@ abstract class Log {
     public abstract void trace(String msg, Object... args);
 
     /**
-     * Prints the throwable stack trace and throws a <tt>RuntimeException</tt>
+     * Prints the throwable stack trace and throws a <code>RuntimeException</code>
      * 
-     * @param message the message to include in the <tt>RuntimeException</tt>
+     * @param message the message to include in the <code>RuntimeException</code>
      * @param t the throwable to print the stack trace for 
      */
     public abstract void fatal(String message, Throwable t);
diff --git a/src/main/java/org/apache/sling/cta/impl/OkHttpTimeoutTransformer.java b/src/main/java/org/apache/sling/cta/impl/OkHttpTimeoutTransformer.java
index 7be4cc6..1007805 100644
--- a/src/main/java/org/apache/sling/cta/impl/OkHttpTimeoutTransformer.java
+++ b/src/main/java/org/apache/sling/cta/impl/OkHttpTimeoutTransformer.java
@@ -21,8 +21,8 @@ import javassist.bytecode.Descriptor;
 /**
  * Sets timeouts for HTTP calls done using <em>OkHttp 3.x</em>
  * 
- * <p>It inserts two calls to <tt>okhttp3.OkHttpClient$Builder</tt> that set default
- * values for <tt>connectTimeout</tt> and <tt>readTimeout</tt>.</p>
+ * <p>It inserts two calls to <code>okhttp3.OkHttpClient$Builder</code> that set default
+ * values for <code>connectTimeout</code> and <code>readTimeout</code>.</p>
  */
 public class OkHttpTimeoutTransformer extends UpdateFieldsInConstructorTimeoutTransformer {
 
diff --git a/src/test/java/org/apache/sling/cta/impl/AgentIT.java b/src/test/java/org/apache/sling/cta/impl/AgentIT.java
index eaeb63a..b2c01ee 100644
--- a/src/test/java/org/apache/sling/cta/impl/AgentIT.java
+++ b/src/test/java/org/apache/sling/cta/impl/AgentIT.java
@@ -41,6 +41,8 @@ import java.util.concurrent.TimeUnit;
 
 import org.apache.commons.httpclient.ConnectTimeoutException;
 import org.apache.sling.cta.impl.HttpClientLauncher.ClientType;
+import org.junit.jupiter.api.condition.DisabledOnOs;
+import org.junit.jupiter.api.condition.OS;
 import org.junit.jupiter.api.extension.ExtendWith;
 import org.junit.jupiter.params.ParameterizedTest;
 import org.junit.jupiter.params.provider.Arguments;
@@ -52,7 +54,7 @@ import org.slf4j.LoggerFactory;
 /**
  * Validates that accessing URLs that exhibit connection problems results in a timeouts being fired when the agent is used
  * 
- * <p>This test validates that the agent works when statically loaded, i.e. with a <tt>-javaagent:</tt> flag
+ * <p>This test validates that the agent works when statically loaded, i.e. with a <code>-javaagent:</code> flag
  * passed to the JVM. As such it requires launching a new JVM instance each time, otherwise the results are
  * not valid.</p>
  * 
@@ -76,8 +78,8 @@ public class AgentIT {
         errorDescriptors.put(JavaNet, new ErrorDescriptor(SocketTimeoutException.class, "connect timed out", "Read timed out"));
         errorDescriptors.put(HC3, new ErrorDescriptor(ConnectTimeoutException.class, "The host did not accept the connection within timeout of 3000 ms", "Read timed out"));
         errorDescriptors.put(HC4, new ErrorDescriptor(org.apache.http.conn.ConnectTimeoutException.class, 
-                "Connect to repo1.maven.org:81 \\[.*\\] failed: connect timed out", "Read timed out"));
-        errorDescriptors.put(OkHttp, new ErrorDescriptor(SocketTimeoutException.class, "connect timed out", "timeout"));
+                "Connect to 127\\.0\\.0\\.1:[0-9]+ \\[.*\\] failed: connect timed out", "Read timed out"));
+        errorDescriptors.put(OkHttp, new ErrorDescriptor(SocketTimeoutException.class, "connect timed out", "(timeout|Read timed out)"));
     }
 
     /**
@@ -96,7 +98,7 @@ public class AgentIT {
      */
     static List<Arguments> argumentsMatrix() {
         
-        List<Arguments> args = new ArrayList<Arguments>();
+        List<Arguments> args = new ArrayList<>();
         
         TestTimeouts clientLower = new TestTimeouts.Builder()
             .agentTimeouts(Duration.ofMinutes(1), Duration.ofMinutes(1))
@@ -112,23 +114,23 @@ public class AgentIT {
 
     
     /**
-     * Validates that connecting to a unaccessible port on an existing port fails with a connect 
+     * Validates that connecting to a unaccessible port on localhost fails with a connect 
      * timeout exception
      * 
-     * <p>It is surprisingly hard to simulate a connnection timeout. The most reliable way seems to
-     * be to get a firewall to drop packets, but this is very hard to do portably and safely
-     * in a unit test. The least bad possible solution is to access an URL that we know will timeout
-     * and that is able to sustain additional traffic. Maven Central is a good candidate for that.</p>
+     * <p>This test is disabled on Windows because the {@link MisbehavingServerControl} cannot generate connection
+     * timeouts. The TCP/IP stack seems to behave differently on Windows vs Linux when the backlog is full. On Linux
+     * a connection timeout is triggered, while on Windows the connection is refused.</p
      * 
      * @throws IOException various I/O problems 
      */
     @ParameterizedTest
     @MethodSource("argumentsMatrix")
-    public void connectTimeout(ClientType clientType, TestTimeouts timeouts) throws IOException {
+    @DisabledOnOs(OS.WINDOWS)
+    public void connectTimeout(ClientType clientType, TestTimeouts timeouts, MisbehavingServerControl server) throws IOException {
 
         ErrorDescriptor ed =  requireNonNull(errorDescriptors.get(clientType), "Unhandled clientType " + clientType);
         RecordedThrowable error = assertTimeout(ofSeconds(EXECUTION_TIMEOUT_SECONDS),  
-            () -> runTest("http://repo1.maven.org:81", clientType, timeouts, false));
+            () -> runTest("http://127.0.0.1:" + server.getConnectTimeoutLocalPort(), clientType, timeouts, false));
         
         assertEquals(ed.connectTimeoutClass.getName(), error.className);
         assertTrue(error.message.matches(ed.connectTimeoutMessageRegex), 
@@ -147,10 +149,11 @@ public class AgentIT {
         
         ErrorDescriptor ed =  requireNonNull(errorDescriptors.get(clientType), "Unhandled clientType " + clientType);
         RecordedThrowable error = assertTimeout(ofSeconds(EXECUTION_TIMEOUT_SECONDS),
-           () -> runTest("http://localhost:" + server.getLocalPort(), clientType, timeouts, false));
+           () -> runTest("http://127.0.0.1:" + server.getLocalPort(), clientType, timeouts, false));
 
         assertEquals(SocketTimeoutException.class.getName(), error.className);
-        assertEquals(ed.readTimeoutMessage, error.message);
+        assertTrue(error.message.matches(ed.readTimeoutRegex),
+            "Actual message " + error.message + " did not match regex " + ed.readTimeoutRegex);
     }
     
     @ParameterizedTest
@@ -161,7 +164,7 @@ public class AgentIT {
         server.setHandleDelay(Duration.ofMillis(100));
         
         assertTimeout(ofSeconds(EXECUTION_TIMEOUT_SECONDS),
-                () ->runTest("http://localhost:" + server.getLocalPort(), clientType, TestTimeouts.DEFAULT, true));
+                () ->runTest("http://127.0.0.1:" + server.getLocalPort(), clientType, TestTimeouts.DEFAULT, true));
     }
 
     private RecordedThrowable runTest(String urlSpec, ClientType clientType, TestTimeouts timeouts, boolean expectSuccess) throws IOException, InterruptedException {
diff --git a/src/test/java/org/apache/sling/cta/impl/MisbehavingServerExtension.java b/src/test/java/org/apache/sling/cta/impl/DelayingHttpServer.java
similarity index 53%
copy from src/test/java/org/apache/sling/cta/impl/MisbehavingServerExtension.java
copy to src/test/java/org/apache/sling/cta/impl/DelayingHttpServer.java
index 780c104..874c7fc 100644
--- a/src/test/java/org/apache/sling/cta/impl/MisbehavingServerExtension.java
+++ b/src/test/java/org/apache/sling/cta/impl/DelayingHttpServer.java
@@ -17,6 +17,8 @@
 package org.apache.sling.cta.impl;
 
 import java.io.IOException;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
 import java.time.Duration;
 
 import javax.servlet.ServletException;
@@ -27,64 +29,28 @@ import org.eclipse.jetty.server.Request;
 import org.eclipse.jetty.server.Server;
 import org.eclipse.jetty.server.ServerConnector;
 import org.eclipse.jetty.server.handler.AbstractHandler;
-import org.junit.jupiter.api.extension.AfterEachCallback;
-import org.junit.jupiter.api.extension.BeforeEachCallback;
-import org.junit.jupiter.api.extension.ExtensionContext;
-import org.junit.jupiter.api.extension.ParameterContext;
-import org.junit.jupiter.api.extension.ParameterResolutionException;
-import org.junit.jupiter.api.extension.ParameterResolver;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-/**
- * Provides an Jetty-based local server that can be configured to timeout
- * 
- * <p>After extending a JUnit Jupiter test with this extension, any parameter of type {@link MisbehavingServerControl}
- * will be resolved.</p>
- *
- */
-class MisbehavingServerExtension implements BeforeEachCallback, AfterEachCallback, ParameterResolver, MisbehavingServerControl {
-    
-    private static final Duration DEFAULT_HANDLE_DELAY = Duration.ofSeconds(10);
+public class DelayingHttpServer {
     
     private final Logger logger = LoggerFactory.getLogger(getClass());
+    private Duration handleDelay;
     
-    public int getLocalPort() {
-        return ((ServerConnector) server.getConnectors()[0]).getLocalPort();
+    public DelayingHttpServer(Duration handleDelay) {
+        this.handleDelay = handleDelay;
     }
     
     private Server server;
     
-    private Duration handleDelay = DEFAULT_HANDLE_DELAY;
-    
-    @Override
-    public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext)
-            throws ParameterResolutionException {
-        return parameterContext.getParameter().getType() == MisbehavingServerControl.class;
-    }
-    
-    @Override
-    public Object resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext)
-            throws ParameterResolutionException {
-        if ( parameterContext.getParameter().getType() == MisbehavingServerControl.class )
-            return this;
-        
-        throw new ParameterResolutionException("Unable to get a " + MisbehavingServerControl.class.getSimpleName() + " instance for " + parameterContext);
-    }
-    
-    @Override
-    public void beforeEach(ExtensionContext context) throws Exception {
-        
-        // reset the delay before each execution to make test logic simpler 
-        handleDelay = DEFAULT_HANDLE_DELAY;
-        
-        server = new Server(0);
+    public void start() throws Exception {
+        server = new Server(new InetSocketAddress(InetAddress.getLoopbackAddress(), 0));
         server.setHandler(new AbstractHandler() {
             
             @Override
             public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response)
                     throws IOException, ServletException {
-                logger.info("Waiting for " + handleDelay + " before handling");
+                logger.info("Waiting for {} before handling", handleDelay);
                 try {
                     Thread.sleep(handleDelay.toMillis());
                 } catch (InterruptedException e) {
@@ -102,19 +68,17 @@ class MisbehavingServerExtension implements BeforeEachCallback, AfterEachCallbac
         
         server.start();
     }
-
-    @Override
-    public void afterEach(ExtensionContext context) throws Exception {
-        if ( server == null )
-            return;
-        try {
+    
+    public void stop() throws Exception {
+        if (server != null) {
             server.stop();
-        } catch (Exception e) {
-            logger.info("Failed shutting down server", e);
         }
     }
     
-    @Override
+    public int getLocalPort() {
+        return ((ServerConnector) server.getConnectors()[0]).getLocalPort();
+    }
+
     public void setHandleDelay(Duration handleDelay) {
         this.handleDelay = handleDelay;
     }
diff --git a/src/test/java/org/apache/sling/cta/impl/ErrorDescriptor.java b/src/test/java/org/apache/sling/cta/impl/ErrorDescriptor.java
index d70b76f..0a1865e 100644
--- a/src/test/java/org/apache/sling/cta/impl/ErrorDescriptor.java
+++ b/src/test/java/org/apache/sling/cta/impl/ErrorDescriptor.java
@@ -26,12 +26,12 @@ import org.apache.sling.cta.impl.HttpClientLauncher.ClientType;
 class ErrorDescriptor {
     Class<? extends IOException> connectTimeoutClass;
     String connectTimeoutMessageRegex;
-    String readTimeoutMessage;
+    String readTimeoutRegex;
 
     public ErrorDescriptor(Class<? extends IOException> connectTimeoutClass, String connectTimeoutMessageRegex,
-            String readTimeoutMessage) {
+            String readTimeoutRegex) {
         this.connectTimeoutClass = connectTimeoutClass;
         this.connectTimeoutMessageRegex = connectTimeoutMessageRegex;
-        this.readTimeoutMessage = readTimeoutMessage;
+        this.readTimeoutRegex = readTimeoutRegex;
     }
 }
\ No newline at end of file
diff --git a/src/test/java/org/apache/sling/cta/impl/MisbehavingServerControl.java b/src/test/java/org/apache/sling/cta/impl/MisbehavingServerControl.java
index d18ac2c..10465c5 100644
--- a/src/test/java/org/apache/sling/cta/impl/MisbehavingServerControl.java
+++ b/src/test/java/org/apache/sling/cta/impl/MisbehavingServerControl.java
@@ -42,4 +42,11 @@ public interface MisbehavingServerControl {
      * @param handleDelay the new duration
      */
     void setHandleDelay(Duration handleDelay);
+    
+    /**
+     * Returns the port on which a local server that does not answer to connections is bound
+     * 
+     * @return the port
+     */
+    int getConnectTimeoutLocalPort();
 }
diff --git a/src/test/java/org/apache/sling/cta/impl/MisbehavingServerExtension.java b/src/test/java/org/apache/sling/cta/impl/MisbehavingServerExtension.java
index 780c104..b65fad4 100644
--- a/src/test/java/org/apache/sling/cta/impl/MisbehavingServerExtension.java
+++ b/src/test/java/org/apache/sling/cta/impl/MisbehavingServerExtension.java
@@ -17,16 +17,14 @@
 package org.apache.sling.cta.impl;
 
 import java.io.IOException;
+import java.net.InetAddress;
+import java.net.ServerSocket;
+import java.net.Socket;
 import java.time.Duration;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
 
-import javax.servlet.ServletException;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-
-import org.eclipse.jetty.server.Request;
-import org.eclipse.jetty.server.Server;
-import org.eclipse.jetty.server.ServerConnector;
-import org.eclipse.jetty.server.handler.AbstractHandler;
 import org.junit.jupiter.api.extension.AfterEachCallback;
 import org.junit.jupiter.api.extension.BeforeEachCallback;
 import org.junit.jupiter.api.extension.ExtensionContext;
@@ -49,13 +47,10 @@ class MisbehavingServerExtension implements BeforeEachCallback, AfterEachCallbac
     
     private final Logger logger = LoggerFactory.getLogger(getClass());
     
-    public int getLocalPort() {
-        return ((ServerConnector) server.getConnectors()[0]).getLocalPort();
-    }
-    
-    private Server server;
+    private DelayingHttpServer server;
     
-    private Duration handleDelay = DEFAULT_HANDLE_DELAY;
+    private ServerSocket ss;
+    private List<Socket> sockets = new ArrayList<>();
     
     @Override
     public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext)
@@ -75,47 +70,82 @@ class MisbehavingServerExtension implements BeforeEachCallback, AfterEachCallbac
     @Override
     public void beforeEach(ExtensionContext context) throws Exception {
         
-        // reset the delay before each execution to make test logic simpler 
-        handleDelay = DEFAULT_HANDLE_DELAY;
+        server = new DelayingHttpServer(DEFAULT_HANDLE_DELAY);
+        server.start();
+        
+        // an undocumented feature of ServerSocket is that the backlog size is quietly adjusted 
+        // to be at least 50
+        int backlog = 50;
         
-        server = new Server(0);
-        server.setHandler(new AbstractHandler() {
-            
-            @Override
-            public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response)
-                    throws IOException, ServletException {
-                logger.info("Waiting for " + handleDelay + " before handling");
-                try {
-                    Thread.sleep(handleDelay.toMillis());
-                } catch (InterruptedException e) {
-                    Thread.currentThread().interrupt();
-                    return;
+        // create a server socket that will not accept connections. We do this by controlling the 
+        // backlog size and making sure that it is full before running the test
+        ss = new ServerSocket(0, backlog, InetAddress.getLoopbackAddress());
+        
+        CountDownLatch waitForConnection = new CountDownLatch(1);
+        
+        new Thread(() -> {
+            int activeConnection = 0;
+            try {
+                // completely tie up the server: 1 active connection + backlog full
+                for (int i = 0; i < backlog + 1; i++) {
+                    activeConnection = i;
+                    sockets.add(new Socket("127.0.0.1", ss.getLocalPort()));
                 }
-
-                response.setStatus(HttpServletResponse.SC_NO_CONTENT);
-                if ( baseRequest.getHeader("User-Agent") != null )
-                    response.addHeader("Original-User-Agent", baseRequest.getHeader("User-Agent"));
-                baseRequest.setHandled(true);
-                logger.info("Handled");
+                logger.info("Keeping connections to port {} unavailable" , ss.getLocalPort());
+                waitForConnection.countDown();
+                // Keep the connection open to fill the backlog
+                Thread.sleep(Long.MAX_VALUE);
+            } catch ( InterruptedException e) {
+                Thread.currentThread().interrupt();
+            } catch (IOException e) {
+                logger.info("Failed connecting to server, active connection was {}", activeConnection, e);
+                waitForConnection.countDown();
             }
-        });
+        }).start();
         
-        server.start();
+        waitForConnection.await();
     }
 
     @Override
     public void afterEach(ExtensionContext context) throws Exception {
-        if ( server == null )
-            return;
-        try {
-            server.stop();
-        } catch (Exception e) {
-            logger.info("Failed shutting down server", e);
+        if ( server != null ) {
+            try {
+                server.stop();
+            } catch (Exception e) {
+                logger.info("Failed shutting down server", e);
+            }
+        }
+        
+        if (ss != null) {
+            try {
+                ss.close();
+            } catch (IOException e) {
+                logger.info("Failed closing server socket", e);
+            }
+        }
+        
+        for (Socket s : sockets) {
+            try {
+                s.close();
+            } catch (IOException e) {
+                logger.info("Failed closing socket", e);
+            }
         }
+        sockets.clear();
     }
     
     @Override
     public void setHandleDelay(Duration handleDelay) {
-        this.handleDelay = handleDelay;
+        server.setHandleDelay(handleDelay);
+    }
+
+    @Override
+    public int getLocalPort() {
+        return server.getLocalPort();
+    }
+    
+    @Override
+    public int getConnectTimeoutLocalPort() {
+        return ss.getLocalPort();
     }
 }
\ No newline at end of file
diff --git a/src/test/java/org/apache/sling/cta/impl/OsgiIT.java b/src/test/java/org/apache/sling/cta/impl/OsgiIT.java
index 56264db..17b2752 100644
--- a/src/test/java/org/apache/sling/cta/impl/OsgiIT.java
+++ b/src/test/java/org/apache/sling/cta/impl/OsgiIT.java
@@ -23,20 +23,23 @@ import static org.ops4j.pax.exam.CoreOptions.options;
 import static org.ops4j.pax.exam.CoreOptions.vmOption;
 
 import java.io.IOException;
+import java.net.SocketTimeoutException;
 import java.nio.file.FileSystem;
 import java.nio.file.FileSystems;
 import java.nio.file.Files;
 import java.nio.file.Path;
 import java.nio.file.PathMatcher;
 import java.nio.file.Paths;
+import java.time.Duration;
 import java.util.List;
 import java.util.stream.Collectors;
 
 import org.apache.http.client.methods.CloseableHttpResponse;
 import org.apache.http.client.methods.HttpGet;
-import org.apache.http.conn.ConnectTimeoutException;
 import org.apache.http.impl.client.CloseableHttpClient;
 import org.apache.http.impl.client.HttpClients;
+import org.junit.After;
+import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.ops4j.pax.exam.Configuration;
@@ -53,6 +56,8 @@ import org.ops4j.pax.exam.junit.PaxExam;
 @RunWith(PaxExam.class)
 public class OsgiIT {
     
+    private DelayingHttpServer server;
+
     @Configuration
     public Option[] config() throws IOException {
         
@@ -69,18 +74,38 @@ public class OsgiIT {
         
         return options(
             junitBundles(),
+            mavenBundle("org.apache.felix", "org.apache.felix.configadmin", "1.9.26"),
             mavenBundle("org.apache.httpcomponents", "httpcore-osgi", "4.4.12"),
             mavenBundle("org.apache.httpcomponents", "httpclient-osgi", "4.5.10"),
-            vmOption("-javaagent:" + agentCandidates.get(0) +"=1,1,v")
+            mavenBundle("org.apache.felix", "org.apache.felix.http.servlet-api", "3.0.0"),
+            mavenBundle("org.apache.felix","org.apache.felix.http.jetty","5.1.26"),
+            vmOption("-javaagent:" + agentCandidates.get(0) +"=10000,1,v") // large connect timeout, very small read timeout
         );
     }
+    
+    @Before
+    public void startHttpServer() throws Exception {
+        server = new DelayingHttpServer(Duration.ofSeconds(1));
+        server.start();
+    }
+    
+    @After
+    public void stopHttpServer() {
+        if (server != null) {
+            try {
+                server.stop();
+            } catch (Exception e) {
+                // ignore
+            }
+        }
+    }
 
-    @Test(expected = ConnectTimeoutException.class)
+    @Test(expected = SocketTimeoutException.class)
     public void callTimesOut() throws IOException {
         try ( CloseableHttpClient httpclient = HttpClients.createDefault() ) {
             // the used host does not really matter, the connect timeout of 1 ms
             // should kick in almost instantly
-            HttpGet get = new HttpGet("https://repo1.maven.org/");
+            HttpGet get = new HttpGet("http://127.0.0.1:" + server.getLocalPort() + "/");
             try ( CloseableHttpResponse response = httpclient.execute(get)) {
                 fail("Request should have failed");
             }