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 21 Apr 2004 01:56:21 -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 21 Apr 2004 01:56:25 -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 21 Apr 2004 01:56:25 -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 21 Apr 2004 01:56:26 -0000 @@ -49,6 +49,25 @@ */ public class SimpleHttpConnectionManager implements HttpConnectionManager { + /** + * Since the same connection is about to be reused, make sure the + * previous request was completely processed, and if not + * consume it now. + * @param conn The connection + */ + static void finishLastResponse(HttpConnection conn) { + InputStream lastResponse = conn.getLastResponseInputStream(); + if (lastResponse != null) { + conn.setLastResponseInputStream(null); + try { + lastResponse.close(); + } catch (IOException ioe) { + //FIXME: badness - close to force reconnect. + conn.close(); + } + } + } + /** The http connection */ private HttpConnection httpConnection; @@ -58,6 +77,14 @@ private HttpConnectionManagerParams params = new HttpConnectionManagerParams(); /** + * The time the connection was made idle. + */ + private long idleStartTime = Long.MAX_VALUE; + + public SimpleHttpConnectionManager() { + } + + /** * @see HttpConnectionManager#getConnection(HostConfiguration) */ public HttpConnection getConnection(HostConfiguration hostConfiguration) { @@ -127,6 +154,9 @@ } } + // remove the connection from the timeout handler + idleStartTime = Long.MAX_VALUE; + return httpConnection; } @@ -149,25 +179,9 @@ } finishLastResponse(httpConnection); - } - - /** - * Since the same connection is about to be reused, make sure the - * previous request was completely processed, and if not - * consume it now. - * @param conn The connection - */ - static void finishLastResponse(HttpConnection conn) { - InputStream lastResponse = conn.getLastResponseInputStream(); - if (lastResponse != null) { - conn.setLastResponseInputStream(null); - try { - lastResponse.close(); - } catch (IOException ioe) { - //FIXME: badness - close to force reconnect. - conn.close(); - } - } + + // track the time the connection was made idle + idleStartTime = System.currentTimeMillis(); } /** @@ -195,5 +209,15 @@ 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) { + long maxIdleTime = System.currentTimeMillis() - idleTimeout; + if (idleStartTime <= maxIdleTime) { + httpConnection.close(); + } } } 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 21 Apr 2004 01:56:27 -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 21 Apr 2004 01:56:27 -0000 @@ -86,6 +86,7 @@ suite.addTest(TestHttpParser.suite()); suite.addTest(TestBadContentLength.suite()); suite.addTest(TestEquals.suite()); + suite.addTestSuite(TestIdleConnectionTimeout.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,148 @@ +/* + * $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 + * . + * + */ +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 track 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 the connections to be tracked. */ + private HashSet connections = new HashSet(); + + /** Holds connections ordered by the time they were added. */ + private TreeMap timeAddedToConnection = new TreeMap(); + + /** + * + */ + 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/java/org/apache/commons/httpclient/util/IdleConnectionTimeoutThread.java =================================================================== RCS file: src/java/org/apache/commons/httpclient/util/IdleConnectionTimeoutThread.java diff -N src/java/org/apache/commons/httpclient/util/IdleConnectionTimeoutThread.java --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ src/java/org/apache/commons/httpclient/util/IdleConnectionTimeoutThread.java 1 Jan 1970 00:00:00 -0000 @@ -0,0 +1,137 @@ +/* + * $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 + * . + * + */ +package org.apache.commons.httpclient.util; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +import org.apache.commons.httpclient.HttpConnectionManager; + +/** + * A utility class for periodically closing idle connections. + * + * @see org.apache.commons.httpclient.HttpConnectionManager#closeIdleConnections(long) + */ +public class IdleConnectionTimeoutThread extends Thread { + + private List connectionManagers = new ArrayList(); + + private boolean shutdown = false; + + private long timeoutInterval = 1000; + + private long connectionTimeout = 3000; + + public IdleConnectionTimeoutThread() { + setDaemon(true); + } + + /** + * Adds a connection manager to be handled by this class. + * {@link HttpConnectionManager#closeIdleConnections(long)} will be called on the connection + * manager every {@link #setTimeoutInterval(long) timeoutInterval} milliseconds. + * + * @param connectionManager The connection manager to add + */ + public synchronized void addConnectionManager(HttpConnectionManager connectionManager) { + if (shutdown) { + throw new IllegalStateException("IdleConnectionTimeoutThread has been shutdown"); + } + this.connectionManagers.add(connectionManager); + } + + /** + * Removes the connection manager from this class. The idle connections from the connection + * manager will no longer be automatically closed by this class. + * + * @param connectionManager The connection manager to remove + */ + public synchronized void removeConnectionManager(HttpConnectionManager connectionManager) { + if (shutdown) { + throw new IllegalStateException("IdleConnectionTimeoutThread has been shutdown"); + } + this.connectionManagers.remove(connectionManager); + } + + /** + * Closes idle connections. + */ + public synchronized void run() { + while (!shutdown) { + Iterator iter = connectionManagers.iterator(); + + while (iter.hasNext()) { + HttpConnectionManager connectionManager = (HttpConnectionManager) iter.next(); + connectionManager.closeIdleConnections(connectionTimeout); + } + + try { + this.wait(timeoutInterval); + } catch (InterruptedException e) { + } + } + // clear out the connection managers now that we're shutdown + this.connectionManagers.clear(); + } + + /** + * Stops the thread used to close idle connections. This class cannot be used once shutdown. + */ + public synchronized void shutdown() { + this.shutdown = true; + } + + /** + * Sets the timeout value to use when testing for idle connections. + * + * @param connectionTimeout The connection timeout in milliseconds + * + * @see HttpConnectionManager#closeIdleConnections(long) + */ + public void setConnectionTimeout(long connectionTimeout) { + if (shutdown) { + throw new IllegalStateException("IdleConnectionTimeoutThread has been shutdown"); + } + this.connectionTimeout = connectionTimeout; + } + /** + * Sets the interval used by this class between closing idle connections. Idle + * connections will be closed every timeoutInterval milliseconds. + * + * @param timeoutInterval The timeout interval in milliseconds + */ + public void setTimeoutInterval(long timeoutInterval) { + if (shutdown) { + throw new IllegalStateException("IdleConnectionTimeoutThread has been shutdown"); + } + this.timeoutInterval = timeoutInterval; + } + +} Index: src/test/org/apache/commons/httpclient/TestIdleConnectionTimeout.java =================================================================== RCS file: src/test/org/apache/commons/httpclient/TestIdleConnectionTimeout.java diff -N src/test/org/apache/commons/httpclient/TestIdleConnectionTimeout.java --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ src/test/org/apache/commons/httpclient/TestIdleConnectionTimeout.java 1 Jan 1970 00:00:00 -0000 @@ -0,0 +1,191 @@ +/* + * $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 + * . + * + */ +package org.apache.commons.httpclient; + +import org.apache.commons.httpclient.params.HttpConnectionManagerParams; +import org.apache.commons.httpclient.util.IdleConnectionHandler; +import org.apache.commons.httpclient.util.IdleConnectionTimeoutThread; + +/** + */ +public class TestIdleConnectionTimeout extends TestNoHostBase { + /** + * + */ + public TestIdleConnectionTimeout() { + super(); + } + /** + * @param arg0 + */ + public TestIdleConnectionTimeout(String arg0) { + super(arg0); + } + + /** + * Tests that the IdleConnectionHandler correctly closes connections. + */ + public void testHandler() { + + 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("Connection 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()); + } + + /** + * Tests that the IdleConnectionTimeoutThread works correctly. + */ + public void testTimeoutThread() { + + TimeoutHttpConnectionManager cm = new TimeoutHttpConnectionManager(); + + IdleConnectionTimeoutThread timeoutThread = new IdleConnectionTimeoutThread(); + timeoutThread.addConnectionManager(cm); + timeoutThread.setTimeoutInterval(100); + timeoutThread.start(); + + synchronized(this) { + try { + this.wait(250); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + + assertTrue("closeIdleConnections() not called", cm.closed); + + timeoutThread.removeConnectionManager(cm); + cm.closed = false; + + synchronized(this) { + try { + this.wait(250); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + + assertFalse("closeIdleConnections() called", cm.closed); + + timeoutThread.shutdown(); + } + + private static class TimeoutHttpConnectionManager implements HttpConnectionManager { + + public boolean closed = false; + + public void closeIdleConnections(long idleTimeout) { + this.closed = true; + } + + public HttpConnection getConnection(HostConfiguration hostConfiguration, long timeout) + throws HttpException { + return null; + } + + public HttpConnection getConnection(HostConfiguration hostConfiguration) { + return null; + } + + public HttpConnection getConnectionWithTimeout(HostConfiguration hostConfiguration, + long timeout) throws ConnectTimeoutException { + return null; + } + + public HttpConnectionManagerParams getParams() { + return null; + } + + public void releaseConnection(HttpConnection conn) { + } + + public void setParams(HttpConnectionManagerParams params) { + } +} + + 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; + } + } + +}