? changes.txt Index: src/java/org/apache/commons/httpclient/HttpConnection.java =================================================================== RCS file: /usr/local/tigris/data/helm/cvs/repository/httpclient-118/src/java/org/apache/commons/httpclient/HttpConnection.java,v retrieving revision 1.2 retrieving revision 1.3 diff -u -r1.2 -r1.3 --- src/java/org/apache/commons/httpclient/HttpConnection.java 2003/10/31 18:03:19 1.2 +++ src/java/org/apache/commons/httpclient/HttpConnection.java 2003/11/17 21:59:29 1.3 @@ -1248,6 +1248,13 @@ socket.setSendBufferSize(sendBufferSize); } } + + /** + * Returns the time this connection was released. + */ + public long getTimeReleased() { + return timeReleased; + } // -- Timeout Exception /** @@ -1451,4 +1458,7 @@ /** The local interface on which the connection is created, or null for the default */ private InetAddress localAddress; + + /** The time this connection was released. */ + private long timeReleased = -1; } Index: src/java/org/apache/commons/httpclient/MultiThreadedHttpConnectionManager.java =================================================================== RCS file: /usr/local/tigris/data/helm/cvs/repository/httpclient-118/src/java/org/apache/commons/httpclient/MultiThreadedHttpConnectionManager.java,v retrieving revision 1.2 retrieving revision 1.3 diff -u -r1.2 -r1.3 --- src/java/org/apache/commons/httpclient/MultiThreadedHttpConnectionManager.java 2003/10/31 18:03:19 1.2 +++ src/java/org/apache/commons/httpclient/MultiThreadedHttpConnectionManager.java 2003/11/17 21:59:29 1.3 @@ -102,6 +102,9 @@ /** The default maximum number of connections allowed overall */ public static final int DEFAULT_MAX_TOTAL_CONNECTIONS = 20; + + /** The default maximum number of milliseconds to allow a free connection to live */ + public static final long DEFAULT_MAX_IDLE_TIME = 5 * 60 * 1000; // 5 minutes. // ----------------------------------------------------- Instance Variables /** Maximum number of connections allowed per host */ @@ -109,6 +112,9 @@ /** Maximum number of connections allowed overall */ private int maxTotalConnections = DEFAULT_MAX_TOTAL_CONNECTIONS; + + /** Time (in milliseconds) to wait before closing a free connection */ + private long idleConnectionTime = DEFAULT_MAX_IDLE_TIME; /** The value to set when calling setStaleCheckingEnabled() on each connection */ private boolean connectionStaleCheckingEnabled = true; @@ -124,6 +130,9 @@ * garbage collector */ private ReferenceQueue referenceQueue; + + /** the IdleConnectionThread */ + private Thread idleConnectionThread; /** * No-args constructor @@ -136,6 +145,9 @@ this.referenceQueue = new ReferenceQueue(); new ReferenceQueueThread().start(); + + idleConnectionThread = new IdleConnectionThread(); + idleConnectionThread.start(); } /** @@ -200,8 +212,33 @@ public int getMaxTotalConnections() { return maxTotalConnections; } + + /** + * Sets the approximate time to wait before an idle connection is closed. + * This is not an exact time -- the actual time the connection is killed + * may be up to (idleTime * 2) due to the nature of how the free connections + * are polled. A value of 0 means to never kill idle connections. + * + * @param idleTime the the amount of time (in milliseconds) to wait before closing + * an idle connection. + */ + public void setIdleConnectionTime(long idleTime) { + this.idleConnectionTime = idleTime; + synchronized(idleConnectionThread) { + idleConnectionThread.notify(); // wake up, as the time has changed. + } + } /** + * Gets the time to wait before an idle connection is closed. + * + * @return The time (in milliseconds) to wait before an idle connection is closed. + */ + public long getIdleConnectionTime() { + return idleConnectionTime; + } + + /** * @see HttpConnectionManager#getConnection(HostConfiguration) */ public HttpConnection getConnection(HostConfiguration hostConfiguration) { @@ -530,43 +567,66 @@ } return connection; } - + /** - * Close and delete an old, unused connection to make room for a new one. + * Deletes a single connection. */ - public synchronized void deleteLeastUsedConnection() { + private synchronized void deleteConnection(HttpConnection connection) { + HostConfiguration connectionConfiguration = configurationForConnection(connection); - HttpConnection connection = (HttpConnection) freeConnections.removeFirst(); + if (LOG.isDebugEnabled()) { + LOG.debug("Reclaiming unused connection, hostConfig=" + + connectionConfiguration); + } - if (connection != null) { - HostConfiguration connectionConfiguration = configurationForConnection(connection); + connection.close(); - if (LOG.isDebugEnabled()) { - LOG.debug("Reclaiming unused connection, hostConfig=" - + connectionConfiguration); + // make sure this connection will not be cleaned up again when garbage + // collected + for (Iterator iter = referenceToHostConfig.keySet().iterator(); iter.hasNext();) { + WeakReference connectionRef = (WeakReference) iter.next(); + if (connectionRef.get() == connection) { + iter.remove(); + connectionRef.enqueue(); + break; } - - connection.close(); - - // make sure this connection will not be cleaned up again when garbage - // collected - for (Iterator iter = referenceToHostConfig.keySet().iterator(); iter.hasNext();) { - WeakReference connectionRef = (WeakReference) iter.next(); - if (connectionRef.get() == connection) { - iter.remove(); - connectionRef.enqueue(); - break; - } + } + + HostConnectionPool hostPool = getHostPool(connectionConfiguration); + + hostPool.freeConnections.remove(connection); + hostPool.numConnections--; + numConnections--; + } + + + /** + * Closes all connections that have been unused for the specified amount of time. + * @param oldestAllowed the oldest allowed time a connection can have before being reclaimed. + */ + public synchronized void deleteOldConnections(long oldestAllowed) { + if(LOG.isDebugEnabled()) + LOG.debug("Attempting to delete all connections that were released prior to " + + oldestAllowed); + for(Iterator i = freeConnections.iterator(); i.hasNext();) { + HttpConnection conn = (HttpConnection)i.next(); + if(conn.getTimeReleased() <= oldestAllowed) { + deleteConnection(conn); + i.remove(); } - - HostConnectionPool hostPool = getHostPool(connectionConfiguration); - - hostPool.freeConnections.remove(connection); - hostPool.numConnections--; - numConnections--; + } + } + + /** + * Close and delete an old, unused connection to make room for a new one. + */ + public synchronized void deleteLeastUsedConnection() { + HttpConnection connection = (HttpConnection) freeConnections.removeFirst(); + if(connection != null) { + deleteConnection(connection); } else if (LOG.isDebugEnabled()) { LOG.debug("Attempted to reclaim an unused connection but there were none."); - } + } } /** @@ -681,6 +741,42 @@ /** The connection pool the thread is waiting for */ public HostConnectionPool hostConnectionPool; } + + /** + * A thread for managing idle HttpConnections and closing them after + * a specified amount of time. + */ + private class IdleConnectionThread extends Thread { + + /** + * Create an instance of make this a daemon thread. + */ + public IdleConnectionThread() { + super("HttpClient-IdleConnectionThread"); + setDaemon(true); + } + + /** + * Start execution. + */ + public void run() { + while(true) { + synchronized(this) { + try { + if(LOG.isTraceEnabled()) + LOG.trace("Sleeping for: " + idleConnectionTime); + wait(idleConnectionTime); + if(idleConnectionTime > 0) { + long oldestAllowed = System.currentTimeMillis() - idleConnectionTime; + connectionPool.deleteOldConnections(oldestAllowed); + } + } catch(InterruptedException e) { + LOG.debug("IdleConnectionThread interrupted", e); + } + } + } + } + } /** * A thread for listening for HttpConnections reclaimed by the garbage @@ -692,6 +788,7 @@ * Create an instance and make this a daemon thread. */ public ReferenceQueueThread() { + super("HttpClient-ReferenceQueueThread"); setDaemon(true); } @@ -749,6 +846,9 @@ // the wrapped connection private HttpConnection wrappedConnection; + + // the time this connection was released + private long timeReleased = -1; /** * Creates a new HttpConnectionAdapter. @@ -999,6 +1099,7 @@ public void releaseConnection() { if (hasConnection()) { + timeReleased = System.currentTimeMillis(); HttpConnection wrappedConnection = this.wrappedConnection; this.wrappedConnection = null; wrappedConnection.releaseConnection(); @@ -1185,6 +1286,17 @@ wrappedConnection.setSendBufferSize(sendBufferSize); } else { throw new IllegalStateException("Connection has been released"); + } + } + + /** + * Returns the time this connection was released. + */ + public long getTimeReleased() { + if (hasConnection()) { + return wrappedConnection.getTimeReleased(); + } else { + return timeReleased; } }