Index: src/java/org/apache/commons/httpclient/HttpConnectionManager.java =================================================================== retrieving revision 1.19 diff -u -r1.19 HttpConnectionManager.java --- src/java/org/apache/commons/httpclient/HttpConnectionManager.java 18 Apr 2004 23:51:35 -0000 1.19 +++ src/java/org/apache/commons/httpclient/HttpConnectionManager.java 19 Apr 2004 02:08:52 -0000 @@ -117,22 +117,30 @@ void releaseConnection(HttpConnection conn); /** + * Closes connections that have been idle for at least the given amount of time. Only + * connections that are currently owned, not checked out, are subject to idle timeouts. + * + * @param idleTime the minimum idle time, in milliseconds, for connections to be closed + */ + void closeIdleConnections(long idleTimeout); + + /** * Returns {@link HttpConnectionManagerParams parameters} associated * with this connection manager. * - * @since 2.1 + * @since 3.0 * * @see HttpConnectionManagerParams */ - public HttpConnectionManagerParams getParams(); + HttpConnectionManagerParams getParams(); /** * Assigns {@link HttpConnectionManagerParams parameters} for this * connection manager. * - * @since 2.1 + * @since 3.0 * * @see HttpConnectionManagerParams */ - public void setParams(final HttpConnectionManagerParams params); + void setParams(final HttpConnectionManagerParams params); } Index: src/java/org/apache/commons/httpclient/MultiThreadedHttpConnectionManager.java =================================================================== retrieving revision 1.35 diff -u -r1.35 MultiThreadedHttpConnectionManager.java --- src/java/org/apache/commons/httpclient/MultiThreadedHttpConnectionManager.java 18 Apr 2004 23:51:35 -0000 1.35 +++ src/java/org/apache/commons/httpclient/MultiThreadedHttpConnectionManager.java 19 Apr 2004 02:08:56 -0000 @@ -47,6 +47,7 @@ import org.apache.commons.httpclient.params.HttpConnectionManagerParams; import org.apache.commons.httpclient.params.HttpConnectionParams; import org.apache.commons.httpclient.protocol.Protocol; +import org.apache.commons.httpclient.util.IdleConnectionHandler; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -547,6 +548,13 @@ } } + /* (non-Javadoc) + * @see org.apache.commons.httpclient.HttpConnectionManager#closeIdleConnections(long) + */ + public void closeIdleConnections(long idleTimeout) { + connectionPool.closeIdleConnections(idleTimeout); + } + /** * Make the given HttpConnection available for use by other requests. * If another thread is blocked in getConnection() that could use this @@ -640,6 +648,8 @@ */ private final Map mapHosts = new HashMap(); + private IdleConnectionHandler idleConnectionHandler = new IdleConnectionHandler(); + /** The number of created connections */ private int numConnections = 0; @@ -669,6 +679,9 @@ // clear out map hosts mapHosts.clear(); + + // remove all references to connections + idleConnectionHandler.removeAll(); } /** @@ -770,6 +783,9 @@ if (LOG.isDebugEnabled()) { LOG.debug("Getting free connection, hostConfig=" + hostConfiguration); } + + // remove the connection from the timeout handler + idleConnectionHandler.remove(connection); } else if (LOG.isDebugEnabled()) { LOG.debug("There were no free connections to get, hostConfig=" + hostConfiguration); @@ -778,6 +794,14 @@ } /** + * Closes idle connections. + * @param idleTimeout + */ + public synchronized void closeIdleConnections(long idleTimeout) { + idleConnectionHandler.closeIdleConnections(idleTimeout); + } + + /** * Close and delete an old, unused connection to make room for a new one. */ public synchronized void deleteLeastUsedConnection() { @@ -799,6 +823,10 @@ hostPool.freeConnections.remove(connection); hostPool.numConnections--; numConnections--; + + // remove the connection from the timeout handler + idleConnectionHandler.remove(connection); + } else if (LOG.isDebugEnabled()) { LOG.debug("Attempted to reclaim an unused connection but there were none."); } @@ -892,7 +920,10 @@ + connectionConfiguration); numConnections = 1; } - + + // register the connection with the timeout handler + idleConnectionHandler.add(conn); + notifyWaitingThread(hostPool); } } Index: src/java/org/apache/commons/httpclient/ProxyClient.java =================================================================== retrieving revision 1.2 diff -u -r1.2 ProxyClient.java --- src/java/org/apache/commons/httpclient/ProxyClient.java 18 Apr 2004 23:51:35 -0000 1.2 +++ src/java/org/apache/commons/httpclient/ProxyClient.java 19 Apr 2004 02:08:57 -0000 @@ -267,6 +267,9 @@ private HttpParams connectionParams; + public void closeIdleConnections(long idleTimeout) { + } + public HttpConnection getConnection() { return httpConnection; } Index: src/java/org/apache/commons/httpclient/SimpleHttpConnectionManager.java =================================================================== retrieving revision 1.18 diff -u -r1.18 SimpleHttpConnectionManager.java --- src/java/org/apache/commons/httpclient/SimpleHttpConnectionManager.java 18 Apr 2004 23:51:35 -0000 1.18 +++ src/java/org/apache/commons/httpclient/SimpleHttpConnectionManager.java 19 Apr 2004 02:08:57 -0000 @@ -33,6 +33,7 @@ import java.io.InputStream; import org.apache.commons.httpclient.params.HttpConnectionManagerParams; +import org.apache.commons.httpclient.util.IdleConnectionHandler; /** * A connection manager that provides access to a single HttpConnection. This @@ -57,6 +58,11 @@ */ private HttpConnectionManagerParams params = new HttpConnectionManagerParams(); + private IdleConnectionHandler idleConnectionHandler = new IdleConnectionHandler(); + + public SimpleHttpConnectionManager() { + } + /** * @see HttpConnectionManager#getConnection(HostConfiguration) */ @@ -127,6 +133,9 @@ } } + // remove the connection from the timeout handler + idleConnectionHandler.remove(httpConnection); + return httpConnection; } @@ -149,6 +158,9 @@ } finishLastResponse(httpConnection); + + // register the connection with the timeout handler + idleConnectionHandler.add(httpConnection); } /** @@ -195,5 +207,12 @@ throw new IllegalArgumentException("Parameters may not be null"); } this.params = params; + } + + /* (non-Javadoc) + * @see org.apache.commons.httpclient.HttpConnectionManager#closeIdleConnections(long) + */ + public void closeIdleConnections(long idleTimeout) { + idleConnectionHandler.closeIdleConnections(idleTimeout); } } Index: src/test/org/apache/commons/httpclient/NoHostHttpConnectionManager.java =================================================================== retrieving revision 1.5 diff -u -r1.5 NoHostHttpConnectionManager.java --- src/test/org/apache/commons/httpclient/NoHostHttpConnectionManager.java 22 Feb 2004 18:08:49 -0000 1.5 +++ src/test/org/apache/commons/httpclient/NoHostHttpConnectionManager.java 19 Apr 2004 02:08:58 -0000 @@ -45,9 +45,15 @@ private boolean connectionReleased = false; private HttpConnectionManagerParams params = new HttpConnectionManagerParams(); - + public NoHostHttpConnectionManager() { setConnection(new SimpleHttpConnection()); + } + + /** + * This method currently does nothing. + */ + public void closeIdleConnections(long idleTimeout) { } /** Index: src/test/org/apache/commons/httpclient/TestNoHost.java =================================================================== retrieving revision 1.34 diff -u -r1.34 TestNoHost.java --- src/test/org/apache/commons/httpclient/TestNoHost.java 12 Apr 2004 10:30:46 -0000 1.34 +++ src/test/org/apache/commons/httpclient/TestNoHost.java 19 Apr 2004 02:08:58 -0000 @@ -86,6 +86,7 @@ suite.addTest(TestHttpParser.suite()); suite.addTest(TestBadContentLength.suite()); suite.addTest(TestEquals.suite()); + suite.addTestSuite(TestIdleConnectionQueue.class); return suite; } Index: src/java/org/apache/commons/httpclient/util/IdleConnectionHandler.java =================================================================== RCS file: src/java/org/apache/commons/httpclient/util/IdleConnectionHandler.java diff -N src/java/org/apache/commons/httpclient/util/IdleConnectionHandler.java --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ src/java/org/apache/commons/httpclient/util/IdleConnectionHandler.java 1 Jan 1970 00:00:00 -0000 @@ -0,0 +1,152 @@ +/* + * $Header: $ + * $Revision: $ + * $Date: $ + * + * ==================================================================== + * + * Copyright 2004 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * . + * + * [Additional notices, if required by prior licensing conditions] + * + */ +package org.apache.commons.httpclient.util; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.TreeMap; + +import org.apache.commons.httpclient.HttpConnection; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +/** + * A helper class for connection managers to close idle connections. + * + *

This class is not synchronized.

+ * + * @see org.apache.commons.httpclient.HttpConnectionManager#closeIdleConnections(long) + */ +public class IdleConnectionHandler { + + private static final Log LOG = LogFactory.getLog(IdleConnectionHandler.class); + + // holds connections to be tracked + private HashSet connections = new HashSet(); + + // holds connections ordered by the timeout they were added + private TreeMap timeAddedToConnection = new TreeMap(); + + private Thread timeoutThread; + + /** + * + */ + public IdleConnectionHandler() { + super(); + } + + /** + * Registers the given connection with this handler. The connection will be held until + * {@link #remove(HttpConnection)} or {@link #closeIdleConnections(long)} is called. + * + * @param connection the connection to add + * + * @see #remove(HttpConnection) + */ + public void add(HttpConnection connection) { + + Long timeAdded = new Long(System.currentTimeMillis()); + + if (LOG.isDebugEnabled()) { + LOG.debug("Adding connection at: " + timeAdded); + } + + List tempList = (List) timeAddedToConnection.get(timeAdded); + if (tempList == null) { + tempList = new ArrayList(); + timeAddedToConnection.put(timeAdded, tempList); + } + tempList.add(connection); + connections.add(connection); + } + + /** + * Removes the given connection from the list of connections to be closed when idle. + * @param connection + */ + public void remove(HttpConnection connection) { + // TODO what about connections that are removed, and then readded before + // the original timeout has occurred? + connections.remove(connection); + } + + /** + * Removes all connections referenced by this handler. + */ + public void removeAll() { + this.connections.clear(); + this.timeAddedToConnection.clear(); + } + + /** + * Closes connections that have been idle for at least the given amount of time. + * + * @param idleTime the minimum idle time, in milliseconds, for connections to be closed + */ + public void closeIdleConnections(long idleTime) { + + // the latest time for which connections will be closed + long idleTimeout = System.currentTimeMillis() - idleTime; + + if (LOG.isDebugEnabled()) { + LOG.debug("Checking for connections, idleTimeout: " + idleTimeout); + } + + Iterator timeoutIter = timeAddedToConnection.keySet().iterator(); + + while (timeoutIter.hasNext()) { + Long connectionTime = (Long) timeoutIter.next(); + if (connectionTime.longValue() <= idleTimeout) { + if (LOG.isDebugEnabled()) { + LOG.debug("Connections timed out, connection time: " + connectionTime); + } + List tempList = (List) timeAddedToConnection.get(connectionTime); + Iterator connectionIter = tempList.iterator(); + while (connectionIter.hasNext()) { + HttpConnection conn = (HttpConnection) connectionIter.next(); + // only close this connection if it has not been removed + if (connections.remove(conn)) { + LOG.debug("Closing connection"); + conn.close(); + } else { + LOG.debug("NOT closing connection"); + } + } + timeoutIter.remove(); + } else { + break; + } + } + } +} Index: src/test/org/apache/commons/httpclient/TestIdleConnectionQueue.java =================================================================== RCS file: src/test/org/apache/commons/httpclient/TestIdleConnectionQueue.java diff -N src/test/org/apache/commons/httpclient/TestIdleConnectionQueue.java --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ src/test/org/apache/commons/httpclient/TestIdleConnectionQueue.java 1 Jan 1970 00:00:00 -0000 @@ -0,0 +1,120 @@ +/* + * $Header: $ + * $Revision: $ + * $Date: $ + * + * ==================================================================== + * + * Copyright 2004 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * . + * + * [Additional notices, if required by prior licensing conditions] + * + */ +package org.apache.commons.httpclient; + +import org.apache.commons.httpclient.util.IdleConnectionHandler; + +/** + */ +public class TestIdleConnectionQueue extends TestNoHostBase { + /** + * + */ + public TestIdleConnectionQueue() { + super(); + } + /** + * @param arg0 + */ + public TestIdleConnectionQueue(String arg0) { + super(arg0); + } + + /** + * Tests that the IdleConnectionHandler correctly closes connections. + */ + public void testClose() { + + TimeoutHttpConnection connection = new TimeoutHttpConnection(); + + IdleConnectionHandler handler = new IdleConnectionHandler(); + + handler.add(connection); + + synchronized(this) { + try { + this.wait(250); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + + handler.closeIdleConnections(100); + + assertTrue("Connected not closed", connection.isClosed()); + + connection.setClosed(false); + + handler.remove(connection); + + synchronized(this) { + try { + this.wait(250); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + + handler.closeIdleConnections(100); + + assertFalse("Connection closed", connection.isClosed()); + } + + private static class TimeoutHttpConnection extends HttpConnection { + + private boolean closed = false;; + + public TimeoutHttpConnection() { + super("fake-host", 80); + } + + /** + * @return Returns the closed. + */ + public boolean isClosed() { + return closed; + } + /** + * @param closed The closed to set. + */ + public void setClosed(boolean closed) { + this.closed = closed; + } + + /* (non-Javadoc) + * @see org.apache.commons.httpclient.HttpConnection#close() + */ + public void close() { + closed = true; + } + } + +}