From 1e70d5a39e3e806ca51676313ba2061dde999cec Mon Sep 17 00:00:00 2001 From: Oleg Kalnichevski Date: Sat, 25 Sep 2010 06:35:07 +0000 Subject: [PATCH] HTTPCLIENT-1000: Maximum connection lifetimes settings for ThreadSafeClientConnManager. Contributed by Michajlo Matijkiw git-svn-id: https://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk@1001144 13f79535-47bb-0310-9956-ffa450edef68 --- RELEASE_NOTES.txt | 3 ++ .../http/impl/conn/tsccm/BasicPoolEntry.java | 34 +++++++++++++- .../http/impl/conn/tsccm/ConnPoolByRoute.java | 20 +++++++- .../tsccm/ThreadSafeClientConnManager.java | 22 +++++++-- .../http/impl/conn/TestTSCCMWithServer.java | 47 ++++++++++++++++++- .../impl/conn/tsccm/TestSpuriousWakeup.java | 8 ++-- 6 files changed, 121 insertions(+), 13 deletions(-) diff --git a/RELEASE_NOTES.txt b/RELEASE_NOTES.txt index 5f471d0d6..1f06dc3a7 100644 --- a/RELEASE_NOTES.txt +++ b/RELEASE_NOTES.txt @@ -1,6 +1,9 @@ Changes since 4.1 ALPHA2 ------------------- +* [HTTPCLIENT-1000] Maximum connection lifetimes settings for ThreadSafeClientConnManager. + Contributed by Michajlo Matijkiw + * [HTTPCLIENT-960] HttpMultipart doesn't generate Content-Type header for binary parts in BROWSER_COMPATIBLE mode. Contributed by Oleg Kalnichevski diff --git a/httpclient/src/main/java/org/apache/http/impl/conn/tsccm/BasicPoolEntry.java b/httpclient/src/main/java/org/apache/http/impl/conn/tsccm/BasicPoolEntry.java index cb91bc4b2..ecbd1bc76 100644 --- a/httpclient/src/main/java/org/apache/http/impl/conn/tsccm/BasicPoolEntry.java +++ b/httpclient/src/main/java/org/apache/http/impl/conn/tsccm/BasicPoolEntry.java @@ -46,6 +46,7 @@ public class BasicPoolEntry extends AbstractPoolEntry { private final long created; private long updated; + private long validUntil; private long expiry; /** @@ -60,6 +61,8 @@ public class BasicPoolEntry extends AbstractPoolEntry { 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,32 @@ public class BasicPoolEntry extends AbstractPoolEntry { */ 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 + * + * @since 4.1 + */ + 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 +139,23 @@ public class BasicPoolEntry extends AbstractPoolEntry { 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); } /** diff --git a/httpclient/src/main/java/org/apache/http/impl/conn/tsccm/ConnPoolByRoute.java b/httpclient/src/main/java/org/apache/http/impl/conn/tsccm/ConnPoolByRoute.java index 07db8f231..7d4665f3c 100644 --- a/httpclient/src/main/java/org/apache/http/impl/conn/tsccm/ConnPoolByRoute.java +++ b/httpclient/src/main/java/org/apache/http/impl/conn/tsccm/ConnPoolByRoute.java @@ -89,6 +89,10 @@ public class ConnPoolByRoute extends AbstractConnPool { //TODO: remove dependenc /** Map of route-specific pools */ protected final Map routeToPool; + + private final long connTTL; + + private final TimeUnit connTTLTimeUnit; protected volatile boolean shutdown; @@ -105,6 +109,18 @@ public class ConnPoolByRoute extends AbstractConnPool { //TODO: remove dependenc final ClientConnectionOperator operator, final ConnPerRoute connPerRoute, int maxTotalConnections) { + this(operator, connPerRoute, maxTotalConnections, -1, TimeUnit.MILLISECONDS); + } + + /** + * @since 4.1 + */ + public ConnPoolByRoute( + final ClientConnectionOperator operator, + final ConnPerRoute connPerRoute, + int maxTotalConnections, + long connTTL, + final TimeUnit connTTLTimeUnit) { super(); if (operator == null) { throw new IllegalArgumentException("Connection operator may not be null"); @@ -120,6 +136,8 @@ public class ConnPoolByRoute extends AbstractConnPool { //TODO: remove dependenc this.freeConnections = createFreeConnQueue(); this.waitingThreads = createWaitingThreadQueue(); this.routeToPool = createRouteToPoolMap(); + this.connTTL = connTTL; + this.connTTLTimeUnit = connTTLTimeUnit; } protected Lock getLock() { @@ -532,7 +550,7 @@ public class ConnPoolByRoute extends AbstractConnPool { //TODO: remove dependenc } // 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 { diff --git a/httpclient/src/main/java/org/apache/http/impl/conn/tsccm/ThreadSafeClientConnManager.java b/httpclient/src/main/java/org/apache/http/impl/conn/tsccm/ThreadSafeClientConnManager.java index 80d32104b..fc84bb3d4 100644 --- a/httpclient/src/main/java/org/apache/http/impl/conn/tsccm/ThreadSafeClientConnManager.java +++ b/httpclient/src/main/java/org/apache/http/impl/conn/tsccm/ThreadSafeClientConnManager.java @@ -87,6 +87,20 @@ public class ThreadSafeClientConnManager implements ClientConnectionManager { * @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 + * + * @since 4.1 + */ + public ThreadSafeClientConnManager(final SchemeRegistry schreg, + long connTTL, TimeUnit connTTLTimeUnit) { super(); if (schreg == null) { throw new IllegalArgumentException("Scheme registry may not be null"); @@ -95,10 +109,10 @@ public class ThreadSafeClientConnManager implements ClientConnectionManager { 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 +163,8 @@ public class ThreadSafeClientConnManager implements ClientConnectionManager { * * @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); } /** diff --git a/httpclient/src/test/java/org/apache/http/impl/conn/TestTSCCMWithServer.java b/httpclient/src/test/java/org/apache/http/impl/conn/TestTSCCMWithServer.java index 9be7f4fa2..c3eddb75b 100644 --- a/httpclient/src/test/java/org/apache/http/impl/conn/TestTSCCMWithServer.java +++ b/httpclient/src/test/java/org/apache/http/impl/conn/TestTSCCMWithServer.java @@ -81,9 +81,14 @@ public class TestTSCCMWithServer extends ServerTestBase { * @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 @@ public class TestTSCCMWithServer extends ServerTestBase { } @Test - public void testCloseExpiredConnections() throws Exception { + public void testCloseExpiredIdleConnections() throws Exception { ThreadSafeClientConnManager mgr = createTSCCM(null); mgr.setMaxTotal(1); @@ -389,6 +394,44 @@ public class TestTSCCMWithServer extends ServerTestBase { 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 diff --git a/httpclient/src/test/java/org/apache/http/impl/conn/tsccm/TestSpuriousWakeup.java b/httpclient/src/test/java/org/apache/http/impl/conn/tsccm/TestSpuriousWakeup.java index 30a64c8ce..5b4644add 100644 --- a/httpclient/src/test/java/org/apache/http/impl/conn/tsccm/TestSpuriousWakeup.java +++ b/httpclient/src/test/java/org/apache/http/impl/conn/tsccm/TestSpuriousWakeup.java @@ -27,20 +27,20 @@ package org.apache.http.impl.conn.tsccm; -import java.util.concurrent.locks.Lock; +import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.Condition; +import java.util.concurrent.locks.Lock; 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 @@ public class TestSpuriousWakeup { } @Override - protected ConnPoolByRoute createConnectionPool() { + protected ConnPoolByRoute createConnectionPool(long connTTL, TimeUnit connTTLUnit) { extendedCPBR = new XConnPoolByRoute(connOperator, connPerRoute, 20); // no connection GC required return extendedCPBR;