You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@hc.apache.org by ol...@apache.org on 2008/03/21 12:28:40 UTC

svn commit: r639600 - in /httpcomponents/httpclient/trunk/module-client/src: main/java/org/apache/http/client/ main/java/org/apache/http/client/methods/ main/java/org/apache/http/impl/client/ test/java/org/apache/http/impl/client/ test/java/org/apache/...

Author: olegk
Date: Fri Mar 21 04:28:15 2008
New Revision: 639600

URL: http://svn.apache.org/viewvc?rev=639600&view=rev
Log:
GTTPCLIENT-760: Improved handling of request abort in fringe situations such as request abort immediately after a connection has been allocated but before the release trigger has been set  
Contributed by Sam Berlin <sberlin at gmail.com>
Reviewed by Oleg Kalnichevski


Modified:
    httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/client/ClientRequestDirector.java
    httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/client/HttpClient.java
    httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/client/methods/AbortableHttpRequest.java
    httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/client/methods/HttpRequestBase.java
    httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/impl/client/AbstractHttpClient.java
    httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/impl/client/DefaultClientRequestDirector.java
    httpcomponents/httpclient/trunk/module-client/src/test/java/org/apache/http/impl/client/TestDefaultClientRequestDirector.java
    httpcomponents/httpclient/trunk/module-client/src/test/java/org/apache/http/impl/conn/ExecReqThread.java

Modified: httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/client/ClientRequestDirector.java
URL: http://svn.apache.org/viewvc/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/client/ClientRequestDirector.java?rev=639600&r1=639599&r2=639600&view=diff
==============================================================================
--- httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/client/ClientRequestDirector.java (original)
+++ httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/client/ClientRequestDirector.java Fri Mar 21 04:28:15 2008
@@ -92,11 +92,11 @@
      *
      * @throws HttpException            in case of a problem
      * @throws IOException              in case of an IO problem
-     * @throws InterruptedException     in case of an interrupt
+     *                                     or if the connection was aborted
      */
     HttpResponse execute(HttpHost target, HttpRequest request,
                          HttpContext context)
-        throws HttpException, IOException, InterruptedException
+        throws HttpException, IOException
         ;
 
 

Modified: httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/client/HttpClient.java
URL: http://svn.apache.org/viewvc/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/client/HttpClient.java?rev=639600&r1=639599&r2=639600&view=diff
==============================================================================
--- httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/client/HttpClient.java (original)
+++ httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/client/HttpClient.java Fri Mar 21 04:28:15 2008
@@ -103,11 +103,11 @@
      *
      * @throws HttpException            in case of a problem
      * @throws IOException              in case of an IO problem
-     * @throws InterruptedException     in case of an interrupt
+     *                                     or the connection was aborted
      * <br/><i @@@>timeout exceptions?</i>
      */
     HttpResponse execute(HttpUriRequest request)
-        throws HttpException, IOException, InterruptedException
+        throws HttpException, IOException
         ;
 
 
@@ -128,11 +128,11 @@
      *
      * @throws HttpException    in case of a problem
      * @throws IOException      in case of an IO problem
-     * @throws InterruptedException     in case of an interrupt
+     *                             or the connection was aborted
      * <br/><i @@@>timeout exceptions?</i>
      */
     HttpResponse execute(HttpUriRequest request, HttpContext context)
-        throws HttpException, IOException, InterruptedException
+        throws HttpException, IOException
         ;
 
 
@@ -155,11 +155,11 @@
      *
      * @throws HttpException    in case of a problem
      * @throws IOException      in case of an IO problem
-     * @throws InterruptedException     in case of an interrupt
+     *                             or the connection was aborted
      * <br/><i @@@>timeout exceptions?</i>
      */
     HttpResponse execute(HttpHost target, HttpRequest request)
-        throws HttpException, IOException, InterruptedException
+        throws HttpException, IOException
         ;
 
 
@@ -183,12 +183,12 @@
      *
      * @throws HttpException    in case of a problem
      * @throws IOException      in case of an IO problem
-     * @throws InterruptedException     in case of an interrupt
+     *                             or the connection was aborted
      * <br/><i @@@>timeout exceptions?</i>
      */
     HttpResponse execute(HttpHost target, HttpRequest request,
                          HttpContext context)
-        throws HttpException, IOException, InterruptedException
+        throws HttpException, IOException
         ;
 
 

Modified: httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/client/methods/AbortableHttpRequest.java
URL: http://svn.apache.org/viewvc/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/client/methods/AbortableHttpRequest.java?rev=639600&r1=639599&r2=639600&view=diff
==============================================================================
--- httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/client/methods/AbortableHttpRequest.java (original)
+++ httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/client/methods/AbortableHttpRequest.java Fri Mar 21 04:28:15 2008
@@ -31,12 +31,18 @@
 
 package org.apache.http.client.methods;
 
+import java.io.IOException;
+
+import org.apache.http.client.HttpClient;
+import org.apache.http.conn.ClientConnectionManager;
 import org.apache.http.conn.ClientConnectionRequest;
 import org.apache.http.conn.ConnectionReleaseTrigger;
+import org.apache.http.conn.ManagedClientConnection;
+import org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager;
 
 /**
  * Interface representing an HTTP request that can be aborted by shutting 
- * donw the underying HTTP connection.
+ * down the underlying HTTP connection.
  *
  * @author <a href="mailto:oleg at ural.ru">Oleg Kalnichevski</a>
  *
@@ -47,10 +53,39 @@
  */
 public interface AbortableHttpRequest {
 
-    void setConnectionRequest(ClientConnectionRequest connRequest);
-    
-    void setReleaseTrigger(ConnectionReleaseTrigger releaseTrigger);
+    /**
+     * Sets the {@link ClientConnectionRequest} callback that can be
+     * used to abort a long-lived request for a connection.
+     * If the request is already aborted, throws an {@link IOException}.
+     * 
+     * @see ClientConnectionManager
+     * @see ThreadSafeClientConnManager
+     */
+    void setConnectionRequest(ClientConnectionRequest connRequest) throws IOException;
     
+    /**
+     * Sets the {@link ConnectionReleaseTrigger} callback that can
+     * be used to abort an active connection.
+     * Typically, this will be the {@link ManagedClientConnection} itself.
+     * If the request is already aborted, throws an {@link IOException}.
+     */
+    void setReleaseTrigger(ConnectionReleaseTrigger releaseTrigger) throws IOException;
+
+    /**
+     * Aborts this http request. Any active execution of this method should
+     * return immediately. If the request has not started, it will abort after
+     * the next execution. Aborting this request will cause all subsequent
+     * executions with this request to fail.
+     * 
+     * @see HttpClient#execute(HttpUriRequest)
+     * @see HttpClient#execute(org.apache.http.HttpHost,
+     *      org.apache.http.HttpRequest)
+     * @see HttpClient#execute(HttpUriRequest,
+     *      org.apache.http.protocol.HttpContext)
+     * @see HttpClient#execute(org.apache.http.HttpHost,
+     *      org.apache.http.HttpRequest, org.apache.http.protocol.HttpContext)
+     */
     void abort();
 
 }
+

Modified: httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/client/methods/HttpRequestBase.java
URL: http://svn.apache.org/viewvc/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/client/methods/HttpRequestBase.java?rev=639600&r1=639599&r2=639600&view=diff
==============================================================================
--- httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/client/methods/HttpRequestBase.java (original)
+++ httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/client/methods/HttpRequestBase.java Fri Mar 21 04:28:15 2008
@@ -58,7 +58,7 @@
 
     private final Lock abortLock;
 
-    private volatile boolean aborted;
+    private boolean aborted;
     
     private URI uri;
     private ClientConnectionRequest connRequest;
@@ -97,24 +97,29 @@
         this.uri = uri;
     }
 
-    public void setConnectionRequest(final ClientConnectionRequest connRequest) {
-        if (this.aborted) {
-            return;
-        }
+    public void setConnectionRequest(final ClientConnectionRequest connRequest)
+            throws IOException {
         this.abortLock.lock();
         try {
+            if (this.aborted) {
+                throw new IOException("Request already aborted");
+            }
+            
+            this.releaseTrigger = null;
             this.connRequest = connRequest;
         } finally {
             this.abortLock.unlock();
         }
     }
 
-    public void setReleaseTrigger(final ConnectionReleaseTrigger releaseTrigger) {
-        if (this.aborted) {
-            return;
-        }
+    public void setReleaseTrigger(final ConnectionReleaseTrigger releaseTrigger)
+            throws IOException {
         this.abortLock.lock();
         try {
+            if (this.aborted) {
+                throw new IOException("Request already aborted");
+            }
+            
             this.connRequest = null;
             this.releaseTrigger = releaseTrigger;
         } finally {
@@ -123,24 +128,35 @@
     }
     
     public void abort() {
-        if (this.aborted) {
-            return;
-        }
-        this.aborted = true;
+        ClientConnectionRequest localRequest;
+        ConnectionReleaseTrigger localTrigger;
+        
         this.abortLock.lock();
         try {
-            if (this.connRequest != null) {
-                this.connRequest.abortRequest();
-            }
-            if (this.releaseTrigger != null) {
-                try {
-                    this.releaseTrigger.abortConnection();
-                } catch (IOException ex) {
-                    // ignore
-                }
-            }
+            if (this.aborted) {
+                return;
+            }            
+            this.aborted = true;
+            
+            localRequest = connRequest;
+            localTrigger = releaseTrigger;
         } finally {
             this.abortLock.unlock();
+        }        
+
+        // Trigger the callbacks outside of the lock, to prevent
+        // deadlocks in the scenario where the callbacks have
+        // their own locks that may be used while calling
+        // setReleaseTrigger or setConnectionRequest.
+        if (localRequest != null) {
+            localRequest.abortRequest();
+        }
+        if (localTrigger != null) {
+            try {
+                localTrigger.abortConnection();
+            } catch (IOException ex) {
+                // ignore
+            }
         }
     }
 

Modified: httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/impl/client/AbstractHttpClient.java
URL: http://svn.apache.org/viewvc/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/impl/client/AbstractHttpClient.java?rev=639600&r1=639599&r2=639600&view=diff
==============================================================================
--- httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/impl/client/AbstractHttpClient.java (original)
+++ httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/impl/client/AbstractHttpClient.java Fri Mar 21 04:28:15 2008
@@ -416,7 +416,7 @@
 
     // non-javadoc, see interface HttpClient
     public final HttpResponse execute(HttpUriRequest request)
-        throws HttpException, IOException, InterruptedException {
+        throws HttpException, IOException {
 
         return execute(request, null);
     }
@@ -433,7 +433,7 @@
      */
     public final HttpResponse execute(HttpUriRequest request,
                                       HttpContext context)
-        throws HttpException, IOException, InterruptedException {
+        throws HttpException, IOException {
 
         if (request == null) {
             throw new IllegalArgumentException
@@ -458,7 +458,7 @@
 
     // non-javadoc, see interface HttpClient
     public final HttpResponse execute(HttpHost target, HttpRequest request)
-        throws HttpException, IOException, InterruptedException {
+        throws HttpException, IOException {
 
         return execute(target, request, null);
     }
@@ -467,7 +467,7 @@
     // non-javadoc, see interface HttpClient
     public final HttpResponse execute(HttpHost target, HttpRequest request,
                                       HttpContext context)
-        throws HttpException, IOException, InterruptedException {
+        throws HttpException, IOException {
 
         if (request == null) {
             throw new IllegalArgumentException

Modified: httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/impl/client/DefaultClientRequestDirector.java
URL: http://svn.apache.org/viewvc/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/impl/client/DefaultClientRequestDirector.java?rev=639600&r1=639599&r2=639600&view=diff
==============================================================================
--- httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/impl/client/DefaultClientRequestDirector.java (original)
+++ httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/impl/client/DefaultClientRequestDirector.java Fri Mar 21 04:28:15 2008
@@ -32,6 +32,7 @@
 package org.apache.http.impl.client;
 
 import java.io.IOException;
+import java.io.InterruptedIOException;
 import java.net.URI;
 import java.net.URISyntaxException;
 import java.util.Map;
@@ -267,7 +268,7 @@
     // non-javadoc, see interface ClientRequestDirector
     public HttpResponse execute(HttpHost target, HttpRequest request,
                                 HttpContext context)
-        throws HttpException, IOException, InterruptedException {
+        throws HttpException, IOException {
 
         RoutedRequest roureq = determineRoute(target, request, context);
 
@@ -294,7 +295,14 @@
                     if (orig instanceof AbortableHttpRequest) {
                         ((AbortableHttpRequest) orig).setConnectionRequest(connRequest);
                     }
-                    managedConn = connRequest.getConnection(timeout, TimeUnit.MILLISECONDS);
+                    
+                    try {
+                        managedConn = connRequest.getConnection(timeout, TimeUnit.MILLISECONDS);
+                    } catch(InterruptedException interrupted) {
+                        InterruptedIOException iox = new InterruptedIOException();
+                        iox.initCause(interrupted);
+                        throw iox;
+                    }
                 }
 
                 if (orig instanceof AbortableHttpRequest) {
@@ -444,9 +452,6 @@
         } catch (IOException ex) {
             abortConnection();
             throw ex;
-        } catch (InterruptedException ex) {
-            abortConnection();
-            throw ex;
         } catch (RuntimeException ex) {
             abortConnection();
             throw ex;
@@ -916,7 +921,6 @@
      * @throws IOException      in case of an IO problem
      */
     private void abortConnection() throws IOException {
-
         ManagedClientConnection mcc = managedConn;
         if (mcc != null) {
             // we got here as the result of an exception

Modified: httpcomponents/httpclient/trunk/module-client/src/test/java/org/apache/http/impl/client/TestDefaultClientRequestDirector.java
URL: http://svn.apache.org/viewvc/httpcomponents/httpclient/trunk/module-client/src/test/java/org/apache/http/impl/client/TestDefaultClientRequestDirector.java?rev=639600&r1=639599&r2=639600&view=diff
==============================================================================
--- httpcomponents/httpclient/trunk/module-client/src/test/java/org/apache/http/impl/client/TestDefaultClientRequestDirector.java (original)
+++ httpcomponents/httpclient/trunk/module-client/src/test/java/org/apache/http/impl/client/TestDefaultClientRequestDirector.java Fri Mar 21 04:28:15 2008
@@ -30,6 +30,7 @@
 
 import java.io.IOException;
 import java.net.ConnectException;
+import java.net.URISyntaxException;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicReference;
@@ -38,20 +39,28 @@
 import junit.framework.TestSuite;
 
 import org.apache.http.HttpException;
+import org.apache.http.HttpHost;
 import org.apache.http.HttpRequest;
 import org.apache.http.HttpResponse;
+import org.apache.http.HttpStatus;
+import org.apache.http.ProtocolVersion;
+import org.apache.http.client.methods.AbortableHttpRequest;
 import org.apache.http.client.methods.HttpGet;
 import org.apache.http.conn.ClientConnectionManager;
 import org.apache.http.conn.ClientConnectionRequest;
 import org.apache.http.conn.ConnectionPoolTimeoutException;
+import org.apache.http.conn.ConnectionReleaseTrigger;
 import org.apache.http.conn.ManagedClientConnection;
 import org.apache.http.conn.PlainSocketFactory;
 import org.apache.http.conn.Scheme;
 import org.apache.http.conn.SchemeRegistry;
 import org.apache.http.conn.routing.HttpRoute;
+import org.apache.http.entity.StringEntity;
 import org.apache.http.impl.conn.ClientConnAdapterMockup;
 import org.apache.http.impl.conn.SingleClientConnManager;
+import org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager;
 import org.apache.http.localserver.ServerTestBase;
+import org.apache.http.message.BasicHeader;
 import org.apache.http.mockup.SocketFactoryMockup;
 import org.apache.http.params.BasicHttpParams;
 import org.apache.http.params.HttpParams;
@@ -108,8 +117,140 @@
         httpget.abort();
         
         assertTrue("should have finished get request", getLatch.await(1, TimeUnit.SECONDS));
-        assertTrue("should be instanceof InterruptedException, was: " + throwableRef.get(),
-                throwableRef.get() instanceof InterruptedException);
+        assertTrue("should be instanceof IOException, was: " + throwableRef.get(),
+                throwableRef.get() instanceof IOException);
+        assertTrue("cause should be InterruptedException, was: " + throwableRef.get().getCause(),
+                throwableRef.get().getCause() instanceof InterruptedException);
+    }
+    
+    /**
+     * Tests that an abort called after the connection has been retrieved
+     * but before a release trigger is set does still abort the request.
+     */
+    public void testAbortAfterAllocateBeforeRequest() throws Exception {
+        this.localServer.register("*", new BasicService());
+        
+        CountDownLatch releaseLatch = new CountDownLatch(1);
+        SchemeRegistry registry = new SchemeRegistry();
+        registry.register(new Scheme("http", PlainSocketFactory.getSocketFactory(), 80));
+        
+        SingleClientConnManager conMan = new SingleClientConnManager(new BasicHttpParams(), registry);
+        final AtomicReference<Throwable> throwableRef = new AtomicReference<Throwable>();
+        final CountDownLatch getLatch = new CountDownLatch(1);
+        final DefaultHttpClient client = new DefaultHttpClient(conMan, new BasicHttpParams()); 
+        final HttpContext context = client.getDefaultContext();
+        final HttpGet httpget = new CustomGet("a", releaseLatch);
+        
+        new Thread(new Runnable() {
+            public void run() {
+                try {
+                    client.execute(getServerHttp(), httpget, context);
+                } catch(Throwable t) {
+                    throwableRef.set(t);
+                } finally {
+                    getLatch.countDown();
+                }
+            }
+        }).start();
+        
+        Thread.sleep(100); // Give it a little time to proceed to release...
+        
+        httpget.abort();
+        
+        releaseLatch.countDown();
+        
+        assertTrue("should have finished get request", getLatch.await(1, TimeUnit.SECONDS));
+        assertTrue("should be instanceof IOException, was: " + throwableRef.get(),
+                throwableRef.get() instanceof IOException);
+    }
+    
+    /**
+     * Tests that an abort called completely before execute
+     * still aborts the request.
+     */
+    public void testAbortBeforeExecute() throws Exception {
+        this.localServer.register("*", new BasicService());
+        
+        SchemeRegistry registry = new SchemeRegistry();
+        registry.register(new Scheme("http", PlainSocketFactory.getSocketFactory(), 80));
+        
+        SingleClientConnManager conMan = new SingleClientConnManager(new BasicHttpParams(), registry);
+        final AtomicReference<Throwable> throwableRef = new AtomicReference<Throwable>();
+        final CountDownLatch getLatch = new CountDownLatch(1);
+        final CountDownLatch startLatch = new CountDownLatch(1);
+        final DefaultHttpClient client = new DefaultHttpClient(conMan, new BasicHttpParams()); 
+        final HttpContext context = client.getDefaultContext();
+        final HttpGet httpget = new HttpGet("a");
+        
+        new Thread(new Runnable() {
+            public void run() {
+                try {
+                    try {
+                        if(!startLatch.await(1, TimeUnit.SECONDS))
+                            throw new RuntimeException("Took too long to start!");
+                    } catch(InterruptedException interrupted) {
+                        throw new RuntimeException("Never started!", interrupted);
+                    }
+                    client.execute(getServerHttp(), httpget, context);
+                } catch(Throwable t) {
+                    throwableRef.set(t);
+                } finally {
+                    getLatch.countDown();
+                }
+            }
+        }).start();
+        
+        httpget.abort();
+        startLatch.countDown();
+        
+        assertTrue("should have finished get request", getLatch.await(1, TimeUnit.SECONDS));
+        assertTrue("should be instanceof IOException, was: " + throwableRef.get(),
+                throwableRef.get() instanceof IOException);
+    }
+    
+    /**
+     * Tests that an abort called after a redirect has found a new host
+     * still aborts in the correct place (while trying to get the new
+     * host's route, not while doing the subsequent request).
+     */
+    public void testAbortAfterRedirectedRoute() throws Exception {
+        final int port = this.localServer.getServicePort();
+        this.localServer.register("*", new BasicRedirectService(port));
+        
+        SchemeRegistry registry = new SchemeRegistry();
+        registry.register(new Scheme("http", PlainSocketFactory.getSocketFactory(), 80));
+        
+        CountDownLatch connLatch = new CountDownLatch(1);
+        CountDownLatch awaitLatch = new CountDownLatch(1);
+        ConnMan4 conMan = new ConnMan4(new BasicHttpParams(), registry, connLatch, awaitLatch);
+        final AtomicReference<Throwable> throwableRef = new AtomicReference<Throwable>();
+        final CountDownLatch getLatch = new CountDownLatch(1);
+        final DefaultHttpClient client = new DefaultHttpClient(conMan, new BasicHttpParams()); 
+        final HttpContext context = client.getDefaultContext();
+        final HttpGet httpget = new HttpGet("a");
+        
+        new Thread(new Runnable() {
+            public void run() {
+                try {
+                    HttpHost host = new HttpHost("127.0.0.1", port);
+                    client.execute(host, httpget, context);
+                } catch(Throwable t) {
+                    throwableRef.set(t);
+                } finally {
+                    getLatch.countDown();
+                }
+            }
+        }).start();
+        
+        assertTrue("should have tried to get a connection", connLatch.await(1, TimeUnit.SECONDS));
+        
+        httpget.abort();
+        
+        assertTrue("should have finished get request", getLatch.await(1, TimeUnit.SECONDS));
+        assertTrue("should be instanceof IOException, was: " + throwableRef.get(),
+                throwableRef.get() instanceof IOException);
+        assertTrue("cause should be InterruptedException, was: " + throwableRef.get().getCause(),
+                throwableRef.get().getCause() instanceof InterruptedException);
     }
     
     
@@ -160,6 +301,81 @@
         }
     }
     
+    private static class BasicService implements HttpRequestHandler {
+        public void handle(final HttpRequest request, 
+                final HttpResponse response, 
+                final HttpContext context) throws HttpException, IOException {
+            response.setStatusCode(200);
+            response.setEntity(new StringEntity("Hello World"));
+        }
+    }
+    
+   private class BasicRedirectService implements HttpRequestHandler {
+        private int statuscode = HttpStatus.SC_SEE_OTHER;
+        private int port;
+
+        public BasicRedirectService(int port) {
+            this.port = port;
+        }
+
+        public void handle(final HttpRequest request,
+                final HttpResponse response, final HttpContext context)
+                throws HttpException, IOException {
+            ProtocolVersion ver = request.getRequestLine().getProtocolVersion();
+            response.setStatusLine(ver, this.statuscode);
+            response.addHeader(new BasicHeader("Location", "http://localhost:"
+                    + this.port + "/newlocation/"));
+            response.addHeader(new BasicHeader("Connection", "close"));
+        }
+    }
+
+    private static class ConnMan4 extends ThreadSafeClientConnManager {
+        private final CountDownLatch connLatch;
+        private final CountDownLatch awaitLatch;
+        
+        public ConnMan4(HttpParams params, SchemeRegistry schreg,
+                CountDownLatch connLatch, CountDownLatch awaitLatch) {
+            super(params, schreg);
+            this.connLatch = connLatch;
+            this.awaitLatch = awaitLatch;
+        }
+        
+        @Override
+        public ClientConnectionRequest requestConnection(HttpRoute route) {
+            // If this is the redirect route, stub the return value
+            // so-as to pretend the host is waiting on a slot...
+            if(route.getTargetHost().getHostName().equals("localhost")) {
+                final Thread currentThread = Thread.currentThread();
+                
+                return new ClientConnectionRequest() {
+                    
+                    public void abortRequest() {
+                        currentThread.interrupt();
+                    }
+                    
+                    public ManagedClientConnection getConnection(
+                            long timeout, TimeUnit tunit)
+                            throws InterruptedException,
+                            ConnectionPoolTimeoutException {
+                        connLatch.countDown(); // notify waiter that we're getting a connection
+                        
+                        // zero usually means sleep forever, but CountDownLatch doesn't interpret it that way.
+                        if(timeout == 0)
+                            timeout = Integer.MAX_VALUE;
+                        
+                        if(!awaitLatch.await(timeout, tunit))
+                            throw new ConnectionPoolTimeoutException();
+                        
+                        return new ClientConnAdapterMockup();
+                    }
+                };  
+            } else {
+                return super.requestConnection(route);
+            }
+        }
+    }
+     
+    
     private static class ConnMan3 extends SingleClientConnManager {
         private ManagedClientConnection allocatedConnection;
         private ManagedClientConnection releasedConnection;
@@ -316,6 +532,28 @@
         public void shutdown() {
             throw new UnsupportedOperationException("just a mockup");
         }
+    }
+    
+    private static class CustomGet extends HttpGet {
+        private final CountDownLatch releaseTriggerLatch;
+
+        public CustomGet(String uri, CountDownLatch releaseTriggerLatch) throws URISyntaxException {
+            super(uri);
+            this.releaseTriggerLatch = releaseTriggerLatch;
+        }
+        
+        @Override
+        public void setReleaseTrigger(ConnectionReleaseTrigger releaseTrigger) throws IOException {
+            try {
+                if(!releaseTriggerLatch.await(1, TimeUnit.SECONDS))
+                    throw new RuntimeException("Waited too long...");
+            } catch(InterruptedException ie) {
+                throw new RuntimeException(ie);
+            }
+            
+            super.setReleaseTrigger(releaseTrigger);
+        }
+        
     }
     
 }

Modified: httpcomponents/httpclient/trunk/module-client/src/test/java/org/apache/http/impl/conn/ExecReqThread.java
URL: http://svn.apache.org/viewvc/httpcomponents/httpclient/trunk/module-client/src/test/java/org/apache/http/impl/conn/ExecReqThread.java?rev=639600&r1=639599&r2=639600&view=diff
==============================================================================
--- httpcomponents/httpclient/trunk/module-client/src/test/java/org/apache/http/impl/conn/ExecReqThread.java (original)
+++ httpcomponents/httpclient/trunk/module-client/src/test/java/org/apache/http/impl/conn/ExecReqThread.java Fri Mar 21 04:28:15 2008
@@ -51,7 +51,7 @@
 public class ExecReqThread extends GetConnThread {
 
     protected final ClientConnectionManager conn_manager;
-    protected final RequestSpec 	request_spec;
+    protected final RequestSpec     request_spec;
     protected volatile HttpResponse response;
     protected volatile byte[]       response_data;