Index: httpclient/src/test/java/org/apache/http/impl/conn/tsccm/TestSpuriousWakeup.java =================================================================== --- httpclient/src/test/java/org/apache/http/impl/conn/tsccm/TestSpuriousWakeup.java (revision 1000113) +++ httpclient/src/test/java/org/apache/http/impl/conn/tsccm/TestSpuriousWakeup.java (working copy) @@ -27,20 +27,20 @@ package org.apache.http.impl.conn.tsccm; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.Lock; -import java.util.concurrent.locks.Condition; import org.apache.http.HttpHost; import org.apache.http.conn.ClientConnectionOperator; import org.apache.http.conn.ClientConnectionRequest; import org.apache.http.conn.ManagedClientConnection; +import org.apache.http.conn.params.ConnPerRoute; import org.apache.http.conn.routing.HttpRoute; import org.apache.http.conn.scheme.PlainSocketFactory; import org.apache.http.conn.scheme.Scheme; import org.apache.http.conn.scheme.SchemeRegistry; import org.apache.http.conn.scheme.SchemeSocketFactory; -import org.apache.http.conn.params.ConnPerRoute; - import org.apache.http.impl.conn.GetConnThread; import org.junit.Assert; import org.junit.Test; @@ -101,7 +101,7 @@ } @Override - protected ConnPoolByRoute createConnectionPool() { + protected ConnPoolByRoute createConnectionPool(long connTTL, TimeUnit connTTLUnit) { extendedCPBR = new XConnPoolByRoute(connOperator, connPerRoute, 20); // no connection GC required return extendedCPBR; Index: httpclient/src/test/java/org/apache/http/impl/conn/TestTSCCMWithServer.java =================================================================== --- httpclient/src/test/java/org/apache/http/impl/conn/TestTSCCMWithServer.java (revision 1000113) +++ httpclient/src/test/java/org/apache/http/impl/conn/TestTSCCMWithServer.java (working copy) @@ -81,9 +81,14 @@ * @return a connection manager to test */ public ThreadSafeClientConnManager createTSCCM(SchemeRegistry schreg) { + return createTSCCM(schreg, -1, TimeUnit.MILLISECONDS); + } + + public ThreadSafeClientConnManager createTSCCM(SchemeRegistry schreg, + long connTTL, TimeUnit connTTLTimeUnit) { if (schreg == null) schreg = supportedSchemes; - return new ThreadSafeClientConnManager(schreg); + return new ThreadSafeClientConnManager(schreg, connTTL, connTTLTimeUnit); } /** @@ -354,7 +359,7 @@ } @Test - public void testCloseExpiredConnections() throws Exception { + public void testCloseExpiredIdleConnections() throws Exception { ThreadSafeClientConnManager mgr = createTSCCM(null); mgr.setMaxTotal(1); @@ -389,7 +394,45 @@ mgr.shutdown(); } + + @Test + public void testCloseExpiredTTLConnections() throws Exception { + ThreadSafeClientConnManager mgr = createTSCCM(null, 100, TimeUnit.MILLISECONDS); + mgr.setMaxTotal(1); + + final HttpHost target = getServerHttp(); + final HttpRoute route = new HttpRoute(target, null, false); + + ManagedClientConnection conn = getConnection(mgr, route); + conn.open(route, httpContext, defaultParams); + + Assert.assertEquals("connectionsInPool", 1, mgr.getConnectionsInPool()); + Assert.assertEquals("connectionsInPool(host)", 1, mgr.getConnectionsInPool(route)); + // Release, let remain idle for forever + mgr.releaseConnection(conn, -1, TimeUnit.MILLISECONDS); + + // Released, still active. + Assert.assertEquals("connectionsInPool", 1, mgr.getConnectionsInPool()); + Assert.assertEquals("connectionsInPool(host)", 1, mgr.getConnectionsInPool(route)); + + mgr.closeExpiredConnections(); + + // Time has not expired yet. + Assert.assertEquals("connectionsInPool", 1, mgr.getConnectionsInPool()); + Assert.assertEquals("connectionsInPool(host)", 1, mgr.getConnectionsInPool(route)); + + Thread.sleep(150); + + mgr.closeExpiredConnections(); + + // TTL expired now, connections are destroyed. + Assert.assertEquals("connectionsInPool", 0, mgr.getConnectionsInPool()); + Assert.assertEquals("connectionsInPool(host)", 0, mgr.getConnectionsInPool(route)); + + mgr.shutdown(); + } + /** * Tests releasing connection from #abort method called from the * main execution thread while there is no blocking I/O operation. Index: httpclient/src/main/java/org/apache/http/impl/conn/tsccm/ConnPoolByRoute.java =================================================================== --- httpclient/src/main/java/org/apache/http/impl/conn/tsccm/ConnPoolByRoute.java (revision 1000113) +++ httpclient/src/main/java/org/apache/http/impl/conn/tsccm/ConnPoolByRoute.java (working copy) @@ -89,6 +89,10 @@ /** Map of route-specific pools */ protected final Map routeToPool; + + private final long connTTL; + + private final TimeUnit connTTLTimeUnit; protected volatile boolean shutdown; @@ -105,6 +109,15 @@ final ClientConnectionOperator operator, final ConnPerRoute connPerRoute, int maxTotalConnections) { + this(operator, connPerRoute, maxTotalConnections, -1, TimeUnit.MILLISECONDS); + } + + public ConnPoolByRoute( + final ClientConnectionOperator operator, + final ConnPerRoute connPerRoute, + int maxTotalConnections, + long connTTL, + TimeUnit connTTLTimeUnit) { super(); if (operator == null) { throw new IllegalArgumentException("Connection operator may not be null"); @@ -120,6 +133,8 @@ this.freeConnections = createFreeConnQueue(); this.waitingThreads = createWaitingThreadQueue(); this.routeToPool = createRouteToPoolMap(); + this.connTTL = connTTL; + this.connTTLTimeUnit = connTTLTimeUnit; } protected Lock getLock() { @@ -532,7 +547,7 @@ } // the entry will create the connection when needed - BasicPoolEntry entry = new BasicPoolEntry(op, rospl.getRoute()); + BasicPoolEntry entry = new BasicPoolEntry(op, rospl.getRoute(), connTTL, connTTLTimeUnit); poolLock.lock(); try { Index: httpclient/src/main/java/org/apache/http/impl/conn/tsccm/BasicPoolEntry.java =================================================================== --- httpclient/src/main/java/org/apache/http/impl/conn/tsccm/BasicPoolEntry.java (revision 1000113) +++ httpclient/src/main/java/org/apache/http/impl/conn/tsccm/BasicPoolEntry.java (working copy) @@ -46,6 +46,7 @@ private final long created; private long updated; + private long validUntil; private long expiry; /** @@ -60,6 +61,8 @@ throw new IllegalArgumentException("HTTP route may not be null"); } this.created = System.currentTimeMillis(); + this.validUntil = Long.MAX_VALUE; + this.expiry = this.validUntil; } /** @@ -70,11 +73,30 @@ */ public BasicPoolEntry(ClientConnectionOperator op, HttpRoute route) { + this(op, route, -1, TimeUnit.MILLISECONDS); + } + + /** + * Creates a new pool entry with a specified maximum lifetime. + * + * @param op the connection operator + * @param route the planned route for the connection + * @param connTTL maximum lifetime of this entry, <=0 implies "infinity" + * @param timeunit TimeUnit of connTTL + */ + public BasicPoolEntry(ClientConnectionOperator op, + HttpRoute route, long connTTL, TimeUnit timeunit) { super(op, route); if (route == null) { throw new IllegalArgumentException("HTTP route may not be null"); } this.created = System.currentTimeMillis(); + if (connTTL > 0) { + this.validUntil = this.created + timeunit.toMillis(connTTL); + } else { + this.validUntil = Long.MAX_VALUE; + } + this.expiry = this.validUntil; } protected final OperatedClientConnection getConnection() { @@ -115,17 +137,23 @@ public long getExpiry() { return this.expiry; } + + public long getValidUntil() { + return this.validUntil; + } /** * @since 4.1 */ public void updateExpiry(long time, TimeUnit timeunit) { this.updated = System.currentTimeMillis(); + long newExpiry; if (time > 0) { - this.expiry = this.updated + timeunit.toMillis(time); + newExpiry = this.updated + timeunit.toMillis(time); } else { - this.expiry = Long.MAX_VALUE; + newExpiry = Long.MAX_VALUE; } + this.expiry = Math.min(validUntil, newExpiry); } /** Index: httpclient/src/main/java/org/apache/http/impl/conn/tsccm/ThreadSafeClientConnManager.java =================================================================== --- httpclient/src/main/java/org/apache/http/impl/conn/tsccm/ThreadSafeClientConnManager.java (revision 1000113) +++ httpclient/src/main/java/org/apache/http/impl/conn/tsccm/ThreadSafeClientConnManager.java (working copy) @@ -87,6 +87,18 @@ * @param schreg the scheme registry. */ public ThreadSafeClientConnManager(final SchemeRegistry schreg) { + this(schreg, -1, TimeUnit.MILLISECONDS); + } + + /** + * 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 + */ + public ThreadSafeClientConnManager(final SchemeRegistry schreg, + long connTTL, TimeUnit connTTLTimeUnit) { super(); if (schreg == null) { throw new IllegalArgumentException("Scheme registry may not be null"); @@ -95,10 +107,10 @@ this.schemeRegistry = schreg; this.connPerRoute = new ConnPerRouteBean(); this.connOperator = createConnectionOperator(schreg); - this.pool = createConnectionPool() ; + this.pool = createConnectionPool(connTTL, connTTLTimeUnit) ; this.connectionPool = this.pool; } - + /** * Creates a new thread safe connection manager. * @@ -149,8 +161,8 @@ * * @since 4.1 */ - protected ConnPoolByRoute createConnectionPool() { - return new ConnPoolByRoute(connOperator, connPerRoute, 20); + protected ConnPoolByRoute createConnectionPool(long connTTL, TimeUnit connTTLTimeUnit) { + return new ConnPoolByRoute(connOperator, connPerRoute, 20, connTTL, connTTLTimeUnit); } /**