You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@hc.apache.org by jo...@apache.org on 2011/07/18 18:13:46 UTC

svn commit: r1147947 - in /httpcomponents/httpclient/trunk/httpclient/src: main/java/org/apache/http/client/ main/java/org/apache/http/impl/client/ main/java/org/apache/http/impl/conn/tsccm/ test/java/org/apache/http/impl/client/

Author: jonm
Date: Mon Jul 18 16:13:44 2011
New Revision: 1147947

URL: http://svn.apache.org/viewvc?rev=1147947&view=rev
Log:
HTTPCLIENT-1101: adaptive connection pool sizing. This implements an
additive increase / multiplicative decrease (AIMD) strategy for
on-the-fly connection pool sizing. No default behavior is changed
at the moment (you have to opt into this algorithm). Still needs
documentation.

Added:
    httpcomponents/httpclient/trunk/httpclient/src/main/java/org/apache/http/client/BackoffManager.java   (with props)
    httpcomponents/httpclient/trunk/httpclient/src/main/java/org/apache/http/client/ConnectionBackoffStrategy.java   (with props)
    httpcomponents/httpclient/trunk/httpclient/src/main/java/org/apache/http/impl/client/AIMDBackoffManager.java   (with props)
    httpcomponents/httpclient/trunk/httpclient/src/main/java/org/apache/http/impl/client/Clock.java   (with props)
    httpcomponents/httpclient/trunk/httpclient/src/main/java/org/apache/http/impl/client/DefaultBackoffStrategy.java   (with props)
    httpcomponents/httpclient/trunk/httpclient/src/main/java/org/apache/http/impl/client/NullBackoffStrategy.java   (with props)
    httpcomponents/httpclient/trunk/httpclient/src/main/java/org/apache/http/impl/client/SystemClock.java   (with props)
    httpcomponents/httpclient/trunk/httpclient/src/test/java/org/apache/http/impl/client/MockClock.java   (with props)
    httpcomponents/httpclient/trunk/httpclient/src/test/java/org/apache/http/impl/client/TestAIMDBackoffManager.java   (with props)
    httpcomponents/httpclient/trunk/httpclient/src/test/java/org/apache/http/impl/client/TestDefaultBackoffStrategy.java   (with props)
    httpcomponents/httpclient/trunk/httpclient/src/test/java/org/apache/http/impl/client/TestNullBackoffStrategy.java   (with props)
Modified:
    httpcomponents/httpclient/trunk/httpclient/src/main/java/org/apache/http/impl/client/AbstractHttpClient.java
    httpcomponents/httpclient/trunk/httpclient/src/main/java/org/apache/http/impl/conn/tsccm/ConnPoolByRoute.java
    httpcomponents/httpclient/trunk/httpclient/src/main/java/org/apache/http/impl/conn/tsccm/ThreadSafeClientConnManager.java

Added: httpcomponents/httpclient/trunk/httpclient/src/main/java/org/apache/http/client/BackoffManager.java
URL: http://svn.apache.org/viewvc/httpcomponents/httpclient/trunk/httpclient/src/main/java/org/apache/http/client/BackoffManager.java?rev=1147947&view=auto
==============================================================================
--- httpcomponents/httpclient/trunk/httpclient/src/main/java/org/apache/http/client/BackoffManager.java (added)
+++ httpcomponents/httpclient/trunk/httpclient/src/main/java/org/apache/http/client/BackoffManager.java Mon Jul 18 16:13:44 2011
@@ -0,0 +1,28 @@
+package org.apache.http.client;
+
+import org.apache.http.conn.routing.HttpRoute;
+
+/**
+ * Represents a controller that dynamically adjusts the size
+ * of an available connection pool based on feedback from
+ * using the connections.
+ * 
+ * @since 4.2
+ *
+ */
+public interface BackoffManager {
+
+    /**
+     * Called when we have decided that the result of
+     * using a connection should be interpreted as a
+     * backoff signal.
+     */
+    public void backOff(HttpRoute route);
+    
+    /**
+     * Called when we have determined that the result of
+     * using a connection has succeeded and that we may
+     * probe for more connections.
+     */
+    public void probe(HttpRoute route);
+}

Propchange: httpcomponents/httpclient/trunk/httpclient/src/main/java/org/apache/http/client/BackoffManager.java
------------------------------------------------------------------------------
    svn:eol-style = native

Added: httpcomponents/httpclient/trunk/httpclient/src/main/java/org/apache/http/client/ConnectionBackoffStrategy.java
URL: http://svn.apache.org/viewvc/httpcomponents/httpclient/trunk/httpclient/src/main/java/org/apache/http/client/ConnectionBackoffStrategy.java?rev=1147947&view=auto
==============================================================================
--- httpcomponents/httpclient/trunk/httpclient/src/main/java/org/apache/http/client/ConnectionBackoffStrategy.java (added)
+++ httpcomponents/httpclient/trunk/httpclient/src/main/java/org/apache/http/client/ConnectionBackoffStrategy.java Mon Jul 18 16:13:44 2011
@@ -0,0 +1,38 @@
+package org.apache.http.client;
+
+import org.apache.http.HttpResponse;
+
+/**
+ * When managing a dynamic number of connections for a given route, this
+ * strategy assesses whether a given request execution outcome should
+ * result in a backoff signal or not, based on either examining the
+ * <code>Throwable</code> that resulted or by examining the resulting
+ * response (e.g. for its status code).
+ * 
+ * @since 4.2
+ *
+ */
+public interface ConnectionBackoffStrategy {
+
+    /**
+     * Determines whether seeing the given <code>Throwable</code> as
+     * a result of request execution should result in a backoff
+     * signal.
+     * @param t the <code>Throwable</code> that happened
+     * @return <code>true</code> if a backoff signal should be
+     *   given
+     */
+    boolean shouldBackoff(Throwable t);
+
+    /**
+     * Determines whether receiving the given {@link HttpResponse} as
+     * a result of request execution should result in a backoff
+     * signal. Implementations MUST restrict themselves to examining
+     * the response header and MUST NOT consume any of the response
+     * body, if any.
+     * @param t the <code>HttpResponse</code> that was received
+     * @return <code>true</code> if a backoff signal should be
+     *   given
+     */
+    boolean shouldBackoff(HttpResponse resp);
+}

Propchange: httpcomponents/httpclient/trunk/httpclient/src/main/java/org/apache/http/client/ConnectionBackoffStrategy.java
------------------------------------------------------------------------------
    svn:eol-style = native

Added: httpcomponents/httpclient/trunk/httpclient/src/main/java/org/apache/http/impl/client/AIMDBackoffManager.java
URL: http://svn.apache.org/viewvc/httpcomponents/httpclient/trunk/httpclient/src/main/java/org/apache/http/impl/client/AIMDBackoffManager.java?rev=1147947&view=auto
==============================================================================
--- httpcomponents/httpclient/trunk/httpclient/src/main/java/org/apache/http/impl/client/AIMDBackoffManager.java (added)
+++ httpcomponents/httpclient/trunk/httpclient/src/main/java/org/apache/http/impl/client/AIMDBackoffManager.java Mon Jul 18 16:13:44 2011
@@ -0,0 +1,124 @@
+package org.apache.http.impl.client;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.http.client.BackoffManager;
+import org.apache.http.conn.params.ConnPerRouteBean;
+import org.apache.http.conn.routing.HttpRoute;
+
+/**
+ * The <code>AIMDBackoffManager</code> applies an additive increase,
+ * multiplicative decrease (AIMD) to managing a dynamic limit to
+ * the number of connections allowed to a given host.
+ * 
+ * @since 4.2
+ */
+public class AIMDBackoffManager implements BackoffManager {
+
+    private ConnPerRouteBean connPerRoute;
+    private Clock clock;
+    private long coolDown = 5 * 1000L;
+    private double backoffFactor = 0.5;
+    private int cap = ConnPerRouteBean.DEFAULT_MAX_CONNECTIONS_PER_ROUTE;
+    private Map<HttpRoute,Long> lastRouteProbes =
+        new HashMap<HttpRoute,Long>();
+    private Map<HttpRoute,Long> lastRouteBackoffs =
+        new HashMap<HttpRoute,Long>();
+
+    
+    /**
+     * Creates an <code>AIMDBackoffManager</code> to manage
+     * per-host connection pool sizes represented by the
+     * given {@link ConnPerRouteBean}.
+     * @param connPerRoute per-host routing maximums to
+     *   be managed
+     */
+    public AIMDBackoffManager(ConnPerRouteBean connPerRoute) {
+        this(connPerRoute, new SystemClock());
+    }
+    
+    AIMDBackoffManager(ConnPerRouteBean connPerRoute, Clock clock) {
+        this.clock = clock;
+        this.connPerRoute = connPerRoute;
+    }
+
+    public void backOff(HttpRoute route) {
+        synchronized(connPerRoute) {
+            int curr = connPerRoute.getMaxForRoute(route);
+            Long lastUpdate = getLastUpdate(lastRouteBackoffs, route);
+            long now = clock.getCurrentTime();
+            if (now - lastUpdate < coolDown) return;
+            connPerRoute.setMaxForRoute(route, getBackedOffPoolSize(curr));
+            lastRouteBackoffs.put(route, now);
+        }
+    }
+
+    private int getBackedOffPoolSize(int curr) {
+        if (curr <= 1) return 1;
+        return (int)(Math.floor(backoffFactor * curr));
+    }
+
+    public void probe(HttpRoute route) {
+        synchronized(connPerRoute) {
+            int curr = connPerRoute.getMaxForRoute(route);
+            int max = (curr >= cap) ? cap : curr + 1; 
+            Long lastProbe = getLastUpdate(lastRouteProbes, route);
+            Long lastBackoff = getLastUpdate(lastRouteBackoffs, route);
+            long now = clock.getCurrentTime();
+            if (now - lastProbe < coolDown || now - lastBackoff < coolDown)
+                return; 
+            connPerRoute.setMaxForRoute(route, max);
+            lastRouteProbes.put(route, now);
+        }
+    }
+
+    private Long getLastUpdate(Map<HttpRoute,Long> updates, HttpRoute route) {
+        Long lastUpdate = updates.get(route);
+        if (lastUpdate == null) lastUpdate = 0L;
+        return lastUpdate;
+    }
+
+    /**
+     * Sets the factor to use when backing off; the new
+     * per-host limit will be roughly, the current max times
+     * this factor. <code>Math.floor</code> is applied in the
+     * case of non-integer outcomes to ensure we actually
+     * decrease the pool size. Pool sizes are never decreased
+     * below 1, however. Defaults to 0.5.
+     * @param d must be between 0.0 and 1.0, exclusive.
+     */
+    public void setBackoffFactor(double d) {
+        if (d <= 0.0 || d >= 1.0) {
+            throw new IllegalArgumentException("backoffFactor must be 0.0 < f < 1.0");
+        }
+        backoffFactor = d;
+    }
+    
+    /**
+     * Sets the amount of time, in milliseconds, to wait between
+     * adjustments in pool sizes for a given host, to allow
+     * enough time for the adjustments to take effect. Defaults
+     * to 5000L (5 seconds). 
+     * @param l must be positive
+     */
+    public void setCooldownMillis(long l) {
+        if (coolDown <= 0) {
+            throw new IllegalArgumentException("cooldownMillis must be positive");
+        }
+        coolDown = l;
+    }
+    
+    /**
+     * Sets the absolute maximum per-host connection pool size to
+     * probe up to; defaults to 2 (the default per-host max).
+     * @param cap must be >= 1
+     */
+    public void setPerHostConnectionCap(int cap) {
+        if (cap < 1) {
+            throw new IllegalArgumentException("perHostConnectionCap must be >= 1");
+        }
+        this.cap = cap;
+    }
+
+}

Propchange: httpcomponents/httpclient/trunk/httpclient/src/main/java/org/apache/http/impl/client/AIMDBackoffManager.java
------------------------------------------------------------------------------
    svn:eol-style = native

Modified: httpcomponents/httpclient/trunk/httpclient/src/main/java/org/apache/http/impl/client/AbstractHttpClient.java
URL: http://svn.apache.org/viewvc/httpcomponents/httpclient/trunk/httpclient/src/main/java/org/apache/http/impl/client/AbstractHttpClient.java?rev=1147947&r1=1147946&r2=1147947&view=diff
==============================================================================
--- httpcomponents/httpclient/trunk/httpclient/src/main/java/org/apache/http/impl/client/AbstractHttpClient.java (original)
+++ httpcomponents/httpclient/trunk/httpclient/src/main/java/org/apache/http/impl/client/AbstractHttpClient.java Mon Jul 18 16:13:44 2011
@@ -45,7 +45,9 @@ import org.apache.http.annotation.Guarde
 import org.apache.http.annotation.ThreadSafe;
 import org.apache.http.auth.AuthSchemeRegistry;
 import org.apache.http.client.AuthenticationHandler;
+import org.apache.http.client.BackoffManager;
 import org.apache.http.client.ClientProtocolException;
+import org.apache.http.client.ConnectionBackoffStrategy;
 import org.apache.http.client.CookieStore;
 import org.apache.http.client.CredentialsProvider;
 import org.apache.http.client.HttpClient;
@@ -64,6 +66,7 @@ import org.apache.http.client.utils.URIU
 import org.apache.http.conn.ClientConnectionManager;
 import org.apache.http.conn.ClientConnectionManagerFactory;
 import org.apache.http.conn.ConnectionKeepAliveStrategy;
+import org.apache.http.conn.routing.HttpRoute;
 import org.apache.http.conn.routing.HttpRoutePlanner;
 import org.apache.http.conn.scheme.SchemeRegistry;
 import org.apache.http.cookie.CookieSpecRegistry;
@@ -248,7 +251,14 @@ public abstract class AbstractHttpClient
     @GuardedBy("this")
     private UserTokenHandler userTokenHandler;
 
-
+    /** The connection backoff strategy. */
+    @GuardedBy("this")
+    private ConnectionBackoffStrategy connectionBackoffStrategy;
+    
+    /** The backoff manager. */
+    @GuardedBy("this")
+    private BackoffManager backoffManager;
+    
     /**
      * Creates a new HTTP client.
      *
@@ -363,6 +373,16 @@ public abstract class AbstractHttpClient
         return registry;
     }
 
+    protected BackoffManager createBackoffManager() {
+        return new BackoffManager() {
+            public void backOff(HttpRoute ignored) { }
+            public void probe(HttpRoute ignored) { }
+        };
+    }
+    
+    protected ConnectionBackoffStrategy createConnectionBackoffStrategy() {
+        return new NullBackoffStrategy();
+    }
 
     protected HttpRequestExecutor createRequestExecutor() {
         return new HttpRequestExecutor();
@@ -461,13 +481,22 @@ public abstract class AbstractHttpClient
             supportedAuthSchemes = createAuthSchemeRegistry();
         }
         return supportedAuthSchemes;
-    }
-
-
+    } 
+    
     public synchronized void setAuthSchemes(final AuthSchemeRegistry authSchemeRegistry) {
         supportedAuthSchemes = authSchemeRegistry;
     }
 
+    public synchronized final ConnectionBackoffStrategy getConnectionBackoffStrategy() {
+        if (connectionBackoffStrategy == null) {
+            connectionBackoffStrategy = createConnectionBackoffStrategy();
+        }
+        return connectionBackoffStrategy;
+    }
+    
+    public synchronized void setConnectionBackoffStrategy(final ConnectionBackoffStrategy strategy) {
+        connectionBackoffStrategy = strategy;
+    }
 
     public synchronized final CookieSpecRegistry getCookieSpecs() {
         if (supportedCookieSpecs == null) {
@@ -476,12 +505,21 @@ public abstract class AbstractHttpClient
         return supportedCookieSpecs;
     }
 
-
+    public synchronized final BackoffManager getBackoffManager() {
+        if (backoffManager == null) {
+            backoffManager = createBackoffManager();
+        }
+        return backoffManager;
+    }
+    
+    public synchronized void setBackoffManager(final BackoffManager manager) {
+        backoffManager = manager;
+    }
+    
     public synchronized void setCookieSpecs(final CookieSpecRegistry cookieSpecRegistry) {
         supportedCookieSpecs = cookieSpecRegistry;
     }
 
-
     public synchronized final ConnectionReuseStrategy getConnectionReuseStrategy() {
         if (reuseStrategy == null) {
             reuseStrategy = createConnectionReuseStrategy();
@@ -817,7 +855,33 @@ public abstract class AbstractHttpClient
         }
 
         try {
-            return director.execute(target, request, execContext);
+            HttpHost targetForRoute = (target != null) ? target
+                    : (HttpHost) determineParams(request).getParameter(
+                            ClientPNames.DEFAULT_HOST);
+            HttpRoute route = getRoutePlanner().determineRoute(targetForRoute, request, execContext);
+            
+            HttpResponse out; 
+            try {
+                out = director.execute(target, request, execContext);
+            } catch (RuntimeException re) {
+                if (getConnectionBackoffStrategy().shouldBackoff(re)) {
+                    getBackoffManager().backOff(route);
+                }
+                throw re;
+            } catch (Exception e) {
+                if (getConnectionBackoffStrategy().shouldBackoff(e)) {
+                    getBackoffManager().backOff(route);
+                }
+                if (e instanceof HttpException) throw (HttpException)e;
+                if (e instanceof IOException) throw (IOException)e;
+                throw new RuntimeException("unexpected exception", e);
+            }
+            if (getConnectionBackoffStrategy().shouldBackoff(out)) {
+                getBackoffManager().backOff(route);
+            } else {
+                getBackoffManager().probe(route);
+            }
+            return out;
         } catch(HttpException httpException) {
             throw new ClientProtocolException(httpException);
         }
@@ -851,7 +915,7 @@ public abstract class AbstractHttpClient
                 stateHandler,
                 params);
     }
-
+    
     /**
      * @since 4.1
      */

Added: httpcomponents/httpclient/trunk/httpclient/src/main/java/org/apache/http/impl/client/Clock.java
URL: http://svn.apache.org/viewvc/httpcomponents/httpclient/trunk/httpclient/src/main/java/org/apache/http/impl/client/Clock.java?rev=1147947&view=auto
==============================================================================
--- httpcomponents/httpclient/trunk/httpclient/src/main/java/org/apache/http/impl/client/Clock.java (added)
+++ httpcomponents/httpclient/trunk/httpclient/src/main/java/org/apache/http/impl/client/Clock.java Mon Jul 18 16:13:44 2011
@@ -0,0 +1,17 @@
+package org.apache.http.impl.client;
+
+/**
+ * Interface used to enable easier testing of time-related behavior.
+ * 
+ * @since 4.2
+ *
+ */
+public interface Clock {
+
+    /**
+     * Returns the current time, expressed as the number of
+     * milliseconds since the epoch.
+     * @return current time
+     */
+    long getCurrentTime();
+}

Propchange: httpcomponents/httpclient/trunk/httpclient/src/main/java/org/apache/http/impl/client/Clock.java
------------------------------------------------------------------------------
    svn:eol-style = native

Added: httpcomponents/httpclient/trunk/httpclient/src/main/java/org/apache/http/impl/client/DefaultBackoffStrategy.java
URL: http://svn.apache.org/viewvc/httpcomponents/httpclient/trunk/httpclient/src/main/java/org/apache/http/impl/client/DefaultBackoffStrategy.java?rev=1147947&view=auto
==============================================================================
--- httpcomponents/httpclient/trunk/httpclient/src/main/java/org/apache/http/impl/client/DefaultBackoffStrategy.java (added)
+++ httpcomponents/httpclient/trunk/httpclient/src/main/java/org/apache/http/impl/client/DefaultBackoffStrategy.java Mon Jul 18 16:13:44 2011
@@ -0,0 +1,28 @@
+package org.apache.http.impl.client;
+
+import java.net.ConnectException;
+import java.net.SocketTimeoutException;
+
+import org.apache.http.HttpResponse;
+import org.apache.http.HttpStatus;
+import org.apache.http.client.ConnectionBackoffStrategy;
+
+/**
+ * This {@link ConnectionBackoffStrategy} backs off either for a raw
+ * network socket or connection timeout or if the server explicitly
+ * sends a 503 (Service Unavailable) response.
+ * 
+ * @since 4.2
+ */
+public class DefaultBackoffStrategy implements ConnectionBackoffStrategy {
+
+    public boolean shouldBackoff(Throwable t) {
+        return (t instanceof SocketTimeoutException
+                || t instanceof ConnectException);
+    }
+
+    public boolean shouldBackoff(HttpResponse resp) {
+        return (resp.getStatusLine().getStatusCode() == HttpStatus.SC_SERVICE_UNAVAILABLE);
+    }
+
+}

Propchange: httpcomponents/httpclient/trunk/httpclient/src/main/java/org/apache/http/impl/client/DefaultBackoffStrategy.java
------------------------------------------------------------------------------
    svn:eol-style = native

Added: httpcomponents/httpclient/trunk/httpclient/src/main/java/org/apache/http/impl/client/NullBackoffStrategy.java
URL: http://svn.apache.org/viewvc/httpcomponents/httpclient/trunk/httpclient/src/main/java/org/apache/http/impl/client/NullBackoffStrategy.java?rev=1147947&view=auto
==============================================================================
--- httpcomponents/httpclient/trunk/httpclient/src/main/java/org/apache/http/impl/client/NullBackoffStrategy.java (added)
+++ httpcomponents/httpclient/trunk/httpclient/src/main/java/org/apache/http/impl/client/NullBackoffStrategy.java Mon Jul 18 16:13:44 2011
@@ -0,0 +1,21 @@
+package org.apache.http.impl.client;
+
+import org.apache.http.HttpResponse;
+import org.apache.http.client.ConnectionBackoffStrategy;
+
+/**
+ * This is a {@link ConnectionBackoffStrategy} that never backs off,
+ * for compatibility with existing behavior.
+ * 
+ * @since 4.2
+ */
+public class NullBackoffStrategy implements ConnectionBackoffStrategy {
+
+    public boolean shouldBackoff(Throwable t) {
+        return false;
+    }
+
+    public boolean shouldBackoff(HttpResponse resp) {
+        return false;
+    }
+}

Propchange: httpcomponents/httpclient/trunk/httpclient/src/main/java/org/apache/http/impl/client/NullBackoffStrategy.java
------------------------------------------------------------------------------
    svn:eol-style = native

Added: httpcomponents/httpclient/trunk/httpclient/src/main/java/org/apache/http/impl/client/SystemClock.java
URL: http://svn.apache.org/viewvc/httpcomponents/httpclient/trunk/httpclient/src/main/java/org/apache/http/impl/client/SystemClock.java?rev=1147947&view=auto
==============================================================================
--- httpcomponents/httpclient/trunk/httpclient/src/main/java/org/apache/http/impl/client/SystemClock.java (added)
+++ httpcomponents/httpclient/trunk/httpclient/src/main/java/org/apache/http/impl/client/SystemClock.java Mon Jul 18 16:13:44 2011
@@ -0,0 +1,14 @@
+package org.apache.http.impl.client;
+
+/**
+ * The actual system clock.
+ * 
+ * @since 4.2
+ */
+public class SystemClock implements Clock {
+
+    public long getCurrentTime() {
+        return System.currentTimeMillis();
+    }
+
+}

Propchange: httpcomponents/httpclient/trunk/httpclient/src/main/java/org/apache/http/impl/client/SystemClock.java
------------------------------------------------------------------------------
    svn:eol-style = native

Modified: httpcomponents/httpclient/trunk/httpclient/src/main/java/org/apache/http/impl/conn/tsccm/ConnPoolByRoute.java
URL: http://svn.apache.org/viewvc/httpcomponents/httpclient/trunk/httpclient/src/main/java/org/apache/http/impl/conn/tsccm/ConnPoolByRoute.java?rev=1147947&r1=1147946&r2=1147947&view=diff
==============================================================================
--- httpcomponents/httpclient/trunk/httpclient/src/main/java/org/apache/http/impl/conn/tsccm/ConnPoolByRoute.java (original)
+++ httpcomponents/httpclient/trunk/httpclient/src/main/java/org/apache/http/impl/conn/tsccm/ConnPoolByRoute.java Mon Jul 18 16:13:44 2011
@@ -450,7 +450,7 @@ public class ConnPoolByRoute extends Abs
 
             RouteSpecificPool rospl = getRoutePool(route, true);
 
-            if (reusable) {
+            if (reusable && rospl.getCapacity() >= 0) {
                 if (log.isDebugEnabled()) {
                     String s;
                     if (validDuration > 0) {

Modified: httpcomponents/httpclient/trunk/httpclient/src/main/java/org/apache/http/impl/conn/tsccm/ThreadSafeClientConnManager.java
URL: http://svn.apache.org/viewvc/httpcomponents/httpclient/trunk/httpclient/src/main/java/org/apache/http/impl/conn/tsccm/ThreadSafeClientConnManager.java?rev=1147947&r1=1147946&r2=1147947&view=diff
==============================================================================
--- httpcomponents/httpclient/trunk/httpclient/src/main/java/org/apache/http/impl/conn/tsccm/ThreadSafeClientConnManager.java (original)
+++ httpcomponents/httpclient/trunk/httpclient/src/main/java/org/apache/http/impl/conn/tsccm/ThreadSafeClientConnManager.java Mon Jul 18 16:13:44 2011
@@ -109,16 +109,33 @@ public class ThreadSafeClientConnManager
      */
     public ThreadSafeClientConnManager(final SchemeRegistry schreg,
             long connTTL, TimeUnit connTTLTimeUnit) {
+        this(schreg, connTTL, connTTLTimeUnit, new ConnPerRouteBean());
+    }
+
+    /**
+     * Creates a new thread safe connection manager.
+     *
+     * @param schreg    the scheme registry.
+     * @param connTTL   max connection lifetime, <=0 implies "infinity"
+     * @param connTTLTimeUnit   TimeUnit of connTTL
+     * @param connPerRoute    mapping of maximum connections per route,
+     *   provided as a dependency so it can be managed externally, e.g.
+     *   for dynamic connection pool size management.
+     *
+     * @since 4.2
+     */
+    public ThreadSafeClientConnManager(final SchemeRegistry schreg,
+            long connTTL, TimeUnit connTTLTimeUnit, ConnPerRouteBean connPerRoute) {
         super();
         if (schreg == null) {
             throw new IllegalArgumentException("Scheme registry may not be null");
         }
         this.log = LogFactory.getLog(getClass());
         this.schemeRegistry = schreg;
-        this.connPerRoute = new ConnPerRouteBean();
+        this.connPerRoute = connPerRoute;
         this.connOperator = createConnectionOperator(schreg);
         this.pool = createConnectionPool(connTTL, connTTLTimeUnit) ;
-        this.connectionPool = this.pool;
+        this.connectionPool = this.pool;        
     }
 
     /**

Added: httpcomponents/httpclient/trunk/httpclient/src/test/java/org/apache/http/impl/client/MockClock.java
URL: http://svn.apache.org/viewvc/httpcomponents/httpclient/trunk/httpclient/src/test/java/org/apache/http/impl/client/MockClock.java?rev=1147947&view=auto
==============================================================================
--- httpcomponents/httpclient/trunk/httpclient/src/test/java/org/apache/http/impl/client/MockClock.java (added)
+++ httpcomponents/httpclient/trunk/httpclient/src/test/java/org/apache/http/impl/client/MockClock.java Mon Jul 18 16:13:44 2011
@@ -0,0 +1,15 @@
+package org.apache.http.impl.client;
+
+public class MockClock implements Clock {
+
+    private long t = System.currentTimeMillis();
+    
+    public long getCurrentTime() {
+        return t;
+    }
+    
+    public void setCurrentTime(long now) {
+        t = now;
+    }
+
+}

Propchange: httpcomponents/httpclient/trunk/httpclient/src/test/java/org/apache/http/impl/client/MockClock.java
------------------------------------------------------------------------------
    svn:eol-style = native

Added: httpcomponents/httpclient/trunk/httpclient/src/test/java/org/apache/http/impl/client/TestAIMDBackoffManager.java
URL: http://svn.apache.org/viewvc/httpcomponents/httpclient/trunk/httpclient/src/test/java/org/apache/http/impl/client/TestAIMDBackoffManager.java?rev=1147947&view=auto
==============================================================================
--- httpcomponents/httpclient/trunk/httpclient/src/test/java/org/apache/http/impl/client/TestAIMDBackoffManager.java (added)
+++ httpcomponents/httpclient/trunk/httpclient/src/test/java/org/apache/http/impl/client/TestAIMDBackoffManager.java Mon Jul 18 16:13:44 2011
@@ -0,0 +1,151 @@
+package org.apache.http.impl.client;
+
+import static org.junit.Assert.*;
+
+import java.util.Random;
+
+import org.apache.http.HttpHost;
+import org.apache.http.client.BackoffManager;
+import org.apache.http.conn.params.ConnPerRouteBean;
+import org.apache.http.conn.routing.HttpRoute;
+import org.junit.Before;
+import org.junit.Test;
+
+
+public class TestAIMDBackoffManager {
+
+    private AIMDBackoffManager impl;
+    private ConnPerRouteBean connPerRoute;
+    private HttpRoute route;
+    private MockClock clock;
+
+    @Before
+    public void setUp() {
+        connPerRoute = new ConnPerRouteBean();
+        route = new HttpRoute(new HttpHost("localhost:80"));
+        clock = new MockClock();
+        impl = new AIMDBackoffManager(connPerRoute, clock);
+        impl.setPerHostConnectionCap(10);
+    }
+    
+    @Test
+    public void isABackoffManager() {
+        assertTrue(impl instanceof BackoffManager);
+    }
+    
+    @Test
+    public void halvesConnectionsOnBackoff() {
+        connPerRoute.setMaxForRoute(route, 4);
+        impl.backOff(route);
+        assertEquals(2, connPerRoute.getMaxForRoute(route));
+    }
+    
+    @Test
+    public void doesNotBackoffBelowOneConnection() {
+        connPerRoute.setMaxForRoute(route, 1);
+        impl.backOff(route);
+        assertEquals(1, connPerRoute.getMaxForRoute(route));        
+    }
+    
+    @Test
+    public void increasesByOneOnProbe() {
+        connPerRoute.setMaxForRoute(route, 2);
+        impl.probe(route);
+        assertEquals(3, connPerRoute.getMaxForRoute(route));        
+    }
+    
+    @Test
+    public void doesNotIncreaseBeyondPerHostMaxOnProbe() {
+        connPerRoute.setDefaultMaxPerRoute(5);
+        connPerRoute.setMaxForRoute(route, 5);
+        impl.setPerHostConnectionCap(5);
+        impl.probe(route);
+        assertEquals(5, connPerRoute.getMaxForRoute(route));
+    }
+    
+    @Test
+    public void backoffDoesNotAdjustDuringCoolDownPeriod() {
+        connPerRoute.setMaxForRoute(route, 4);
+        long now = System.currentTimeMillis();
+        clock.setCurrentTime(now);
+        impl.backOff(route);
+        long max = connPerRoute.getMaxForRoute(route);
+        clock.setCurrentTime(now + 1);
+        impl.backOff(route);
+        assertEquals(max, connPerRoute.getMaxForRoute(route));
+    }
+    
+    @Test
+    public void backoffStillAdjustsAfterCoolDownPeriod() {
+        connPerRoute.setMaxForRoute(route, 8);
+        long now = System.currentTimeMillis();
+        clock.setCurrentTime(now);
+        impl.backOff(route);
+        long max = connPerRoute.getMaxForRoute(route);
+        clock.setCurrentTime(now + 10 * 1000L);
+        impl.backOff(route);
+        assertTrue(max == 1 || max > connPerRoute.getMaxForRoute(route));
+    }
+    
+    @Test
+    public void probeDoesNotAdjustDuringCooldownPeriod() {        
+        connPerRoute.setMaxForRoute(route, 4);
+        long now = System.currentTimeMillis();
+        clock.setCurrentTime(now);
+        impl.probe(route);
+        long max = connPerRoute.getMaxForRoute(route);
+        clock.setCurrentTime(now + 1);
+        impl.probe(route);
+        assertEquals(max, connPerRoute.getMaxForRoute(route));
+    }
+
+    @Test
+    public void probeStillAdjustsAfterCoolDownPeriod() {
+        connPerRoute.setMaxForRoute(route, 8);
+        long now = System.currentTimeMillis();
+        clock.setCurrentTime(now);
+        impl.probe(route);
+        long max = connPerRoute.getMaxForRoute(route);
+        clock.setCurrentTime(now + 10 * 1000L);
+        impl.probe(route);
+        assertTrue(max < connPerRoute.getMaxForRoute(route));
+    }
+    
+    @Test
+    public void willBackoffImmediatelyEvenAfterAProbe() {
+        connPerRoute.setMaxForRoute(route, 8);
+        long now = System.currentTimeMillis();
+        clock.setCurrentTime(now);
+        impl.probe(route);
+        long max = connPerRoute.getMaxForRoute(route);
+        clock.setCurrentTime(now + 1);
+        impl.backOff(route);
+        assertTrue(connPerRoute.getMaxForRoute(route) < max);
+    }
+    
+    @Test
+    public void backOffFactorIsConfigurable() {
+        connPerRoute.setMaxForRoute(route, 10);
+        impl.setBackoffFactor(0.9);
+        impl.backOff(route);
+        assertEquals(9, connPerRoute.getMaxForRoute(route));
+    }
+    
+    @Test
+    public void coolDownPeriodIsConfigurable() {
+        long cd = new Random().nextLong() / 2;
+        if (cd < 0) cd *= -1;
+        if (cd < 1) cd++;
+        long now = System.currentTimeMillis();
+        impl.setCooldownMillis(cd);
+        clock.setCurrentTime(now);
+        impl.probe(route);
+        int max0 = connPerRoute.getMaxForRoute(route);
+        clock.setCurrentTime(now);
+        impl.probe(route);
+        assertEquals(max0, connPerRoute.getMaxForRoute(route));
+        clock.setCurrentTime(now + cd + 1);
+        impl.probe(route);
+        assertTrue(max0 < connPerRoute.getMaxForRoute(route));
+    }
+}

Propchange: httpcomponents/httpclient/trunk/httpclient/src/test/java/org/apache/http/impl/client/TestAIMDBackoffManager.java
------------------------------------------------------------------------------
    svn:eol-style = native

Added: httpcomponents/httpclient/trunk/httpclient/src/test/java/org/apache/http/impl/client/TestDefaultBackoffStrategy.java
URL: http://svn.apache.org/viewvc/httpcomponents/httpclient/trunk/httpclient/src/test/java/org/apache/http/impl/client/TestDefaultBackoffStrategy.java?rev=1147947&view=auto
==============================================================================
--- httpcomponents/httpclient/trunk/httpclient/src/test/java/org/apache/http/impl/client/TestDefaultBackoffStrategy.java (added)
+++ httpcomponents/httpclient/trunk/httpclient/src/test/java/org/apache/http/impl/client/TestDefaultBackoffStrategy.java Mon Jul 18 16:13:44 2011
@@ -0,0 +1,63 @@
+package org.apache.http.impl.client;
+
+import static org.junit.Assert.*;
+
+import java.net.ConnectException;
+import java.net.SocketTimeoutException;
+
+import org.apache.http.HttpResponse;
+import org.apache.http.HttpStatus;
+import org.apache.http.HttpVersion;
+import org.apache.http.client.ConnectionBackoffStrategy;
+import org.apache.http.conn.ConnectionPoolTimeoutException;
+import org.apache.http.message.BasicHttpResponse;
+import org.junit.Before;
+import org.junit.Test;
+
+
+public class TestDefaultBackoffStrategy {
+
+    private DefaultBackoffStrategy impl;
+
+    @Before
+    public void setUp() {
+        impl = new DefaultBackoffStrategy();
+    }
+    
+    @Test
+    public void isABackoffStrategy() {
+        assertTrue(impl instanceof ConnectionBackoffStrategy);
+    }
+    
+    @Test
+    public void backsOffForSocketTimeouts() {
+        assertTrue(impl.shouldBackoff(new SocketTimeoutException()));
+    }
+    
+    @Test
+    public void backsOffForConnectionTimeouts() {
+        assertTrue(impl.shouldBackoff(new ConnectException()));
+    }
+    
+    @Test
+    public void doesNotBackOffForConnectionManagerTimeout() {
+        assertFalse(impl.shouldBackoff(new ConnectionPoolTimeoutException()));
+    }
+    
+    @Test
+    public void backsOffForServiceUnavailable() {
+        HttpResponse resp = new BasicHttpResponse(HttpVersion.HTTP_1_1,
+                HttpStatus.SC_SERVICE_UNAVAILABLE, "Service Unavailable");
+        assertTrue(impl.shouldBackoff(resp));
+    }
+    
+    @Test
+    public void doesNotBackOffForNon503StatusCodes() {
+        for(int i = 100; i <= 599; i++) {
+            if (i == HttpStatus.SC_SERVICE_UNAVAILABLE) continue;
+            HttpResponse resp = new BasicHttpResponse(HttpVersion.HTTP_1_1,
+                    i, "Foo");
+            assertFalse(impl.shouldBackoff(resp));
+        }
+    }
+}

Propchange: httpcomponents/httpclient/trunk/httpclient/src/test/java/org/apache/http/impl/client/TestDefaultBackoffStrategy.java
------------------------------------------------------------------------------
    svn:eol-style = native

Added: httpcomponents/httpclient/trunk/httpclient/src/test/java/org/apache/http/impl/client/TestNullBackoffStrategy.java
URL: http://svn.apache.org/viewvc/httpcomponents/httpclient/trunk/httpclient/src/test/java/org/apache/http/impl/client/TestNullBackoffStrategy.java?rev=1147947&view=auto
==============================================================================
--- httpcomponents/httpclient/trunk/httpclient/src/test/java/org/apache/http/impl/client/TestNullBackoffStrategy.java (added)
+++ httpcomponents/httpclient/trunk/httpclient/src/test/java/org/apache/http/impl/client/TestNullBackoffStrategy.java Mon Jul 18 16:13:44 2011
@@ -0,0 +1,33 @@
+package org.apache.http.impl.client;
+
+import static org.junit.Assert.*;
+
+import org.apache.http.HttpResponse;
+import org.apache.http.HttpStatus;
+import org.apache.http.HttpVersion;
+import org.apache.http.message.BasicHttpResponse;
+import org.junit.Before;
+import org.junit.Test;
+
+
+public class TestNullBackoffStrategy {
+
+    private NullBackoffStrategy impl;
+
+    @Before
+    public void setUp() {
+        impl = new NullBackoffStrategy();
+    }
+    
+    @Test
+    public void doesNotBackoffForThrowables() {
+        assertFalse(impl.shouldBackoff(new Exception()));
+    }
+    
+    @Test
+    public void doesNotBackoffForResponses() {
+        HttpResponse resp = new BasicHttpResponse(HttpVersion.HTTP_1_1,
+                HttpStatus.SC_SERVICE_UNAVAILABLE, "Service Unavailable");
+        assertFalse(impl.shouldBackoff(resp));
+    }
+}

Propchange: httpcomponents/httpclient/trunk/httpclient/src/test/java/org/apache/http/impl/client/TestNullBackoffStrategy.java
------------------------------------------------------------------------------
    svn:eol-style = native