Index: src/java/org/apache/commons/httpclient/MultiThreadedHttpConnectionManager.java =================================================================== retrieving revision 1.17.2.7 diff -u -r1.17.2.7 MultiThreadedHttpConnectionManager.java --- src/java/org/apache/commons/httpclient/MultiThreadedHttpConnectionManager.java 22 Feb 2004 18:21:13 -0000 1.17.2.7 +++ src/java/org/apache/commons/httpclient/MultiThreadedHttpConnectionManager.java 11 Mar 2004 03:32:59 -0000 @@ -39,11 +39,11 @@ import java.lang.ref.WeakReference; import java.net.InetAddress; import java.net.SocketException; -import java.util.Collections; import java.util.HashMap; import java.util.Iterator; import java.util.LinkedList; import java.util.Map; +import java.util.WeakHashMap; import org.apache.commons.httpclient.protocol.Protocol; import org.apache.commons.logging.Log; @@ -75,7 +75,7 @@ * A mapping from Reference to ConnectionSource. Used to reclaim resources when connections * are lost to the garbage collector. */ - public static final Map REFERENCE_TO_CONNECTION_SOURCE = Collections.synchronizedMap(new HashMap()); + private static final Map REFERENCE_TO_CONNECTION_SOURCE = new HashMap(); /** * The reference queue used to track when HttpConnections are lost to the @@ -88,9 +88,40 @@ */ private static ReferenceQueueThread REFERENCE_QUEUE_THREAD; - static { - REFERENCE_QUEUE_THREAD = new ReferenceQueueThread(); - REFERENCE_QUEUE_THREAD.start(); + /** + * Holds references to all active instances of this class. + */ + private static WeakHashMap ALL_CONNECTION_MANAGERS = new WeakHashMap(); + + /** + * Shuts down and cleans up resources used by all instances of + * MultiThreadedHttpConnectionManager. All static resources are released, all threads are + * stopped, and {@link #shutdown()} is called on all live instaces of + * MultiThreadedHttpConnectionManager. + * + * @see #shutdown() + */ + public static void shutdownAll() { + + synchronized (REFERENCE_TO_CONNECTION_SOURCE) { + // shutdown all connection managers + synchronized (ALL_CONNECTION_MANAGERS) { + Iterator connIter = ALL_CONNECTION_MANAGERS.keySet().iterator(); + while (connIter.hasNext()) { + MultiThreadedHttpConnectionManager connManager = + (MultiThreadedHttpConnectionManager) connIter.next(); + connIter.remove(); + connManager.shutdown(); + } + } + + // shutdown static resources + if (REFERENCE_QUEUE_THREAD != null) { + REFERENCE_QUEUE_THREAD.shutdown(); + REFERENCE_QUEUE_THREAD = null; + } + REFERENCE_TO_CONNECTION_SOURCE.clear(); + } } /** @@ -120,10 +151,19 @@ source.connectionPool = connectionPool; source.hostConfiguration = hostConfiguration; - REFERENCE_TO_CONNECTION_SOURCE.put( - connection.reference, - source - ); + synchronized (REFERENCE_TO_CONNECTION_SOURCE) { + + // start the reference queue thread if needed + if (REFERENCE_QUEUE_THREAD == null) { + REFERENCE_QUEUE_THREAD = new ReferenceQueueThread(); + REFERENCE_QUEUE_THREAD.start(); + } + + REFERENCE_TO_CONNECTION_SOURCE.put( + connection.reference, + source + ); + } } /** @@ -151,6 +191,8 @@ /** The value to set when calling setStaleCheckingEnabled() on each connection */ private boolean connectionStaleCheckingEnabled = true; + private boolean shutdown = false; + /** Connection Pool */ private ConnectionPool connectionPool; @@ -159,9 +201,25 @@ */ public MultiThreadedHttpConnectionManager() { this.connectionPool = new ConnectionPool(); + synchronized(ALL_CONNECTION_MANAGERS) { + ALL_CONNECTION_MANAGERS.put(this, null); + } } /** + * Shuts down the connection manager and releases all resources. This instance can no longer + * be used once shutdown. Calling this method more than once will have no effect. + */ + public synchronized void shutdown() { + synchronized (connectionPool) { + if (!shutdown) { + shutdown = true; + connectionPool.shutdown(); + } + } + } + + /** * Gets the staleCheckingEnabled value to be set on HttpConnections that are created. * * @return true if stale checking will be enabled on HttpConections @@ -303,6 +361,10 @@ while (connection == null) { + if (shutdown) { + throw new IllegalStateException("Connection factory has been shutdown."); + } + // happen to have a free connection with the right specs // if (hostPool.freeConnections.size() > 0) { @@ -468,6 +530,31 @@ private int numConnections = 0; /** + * Cleans up all connection pool resources. + */ + public synchronized void shutdown() { + + // close all free connections + Iterator iter = freeConnections.iterator(); + while (iter.hasNext()) { + HttpConnection conn = (HttpConnection) iter.next(); + iter.remove(); + conn.close(); + } + + // interrupt all waiting threads + iter = waitingThreads.iterator(); + while (iter.hasNext()) { + WaitingThread waiter = (WaitingThread) iter.next(); + iter.remove(); + waiter.thread.interrupt(); + } + + // clear out map hosts + mapHosts.clear(); + } + + /** * Creates a new connection and returns is for use of the calling method. * * @param hostConfiguration the configuration for the connection @@ -659,6 +746,14 @@ } synchronized (this) { + + if (shutdown) { + // the connection manager has been shutdown, release the connection's + // resources and get out of here + conn.close(); + return; + } + HostConnectionPool hostPool = getHostPool(connectionConfiguration); // Put the connect back in the available list and notify a waiter @@ -735,6 +830,8 @@ */ private static class ReferenceQueueThread extends Thread { + private boolean shutdown = false; + /** * Create an instance and make this a daemon thread. */ @@ -743,6 +840,10 @@ setName("MultiThreadedHttpConnectionManager cleanup"); } + public void shutdown() { + this.shutdown = true; + } + /** * Handles cleaning up for the given connection reference. * @@ -750,7 +851,11 @@ */ private void handleReference(Reference ref) { - ConnectionSource source = (ConnectionSource) REFERENCE_TO_CONNECTION_SOURCE.remove(ref); + ConnectionSource source = null; + + synchronized (REFERENCE_TO_CONNECTION_SOURCE) { + source = (ConnectionSource) REFERENCE_TO_CONNECTION_SOURCE.remove(ref); + } // only clean up for this reference if it is still associated with // a ConnectionSource if (source != null) { @@ -768,9 +873,12 @@ * Start execution. */ public void run() { - while (true) { + while (!shutdown) { try { - Reference ref = REFERENCE_QUEUE.remove(); + // remove the next reference and process it, a timeout + // is used so that the thread does not block indefinitely + // and therefore keep the thread from shutting down + Reference ref = REFERENCE_QUEUE.remove(1000); if (ref != null) { handleReference(ref); } Index: src/test/org/apache/commons/httpclient/TestHttpConnectionManager.java =================================================================== retrieving revision 1.8.2.4 diff -u -r1.8.2.4 TestHttpConnectionManager.java --- src/test/org/apache/commons/httpclient/TestHttpConnectionManager.java 22 Feb 2004 18:21:16 -0000 1.8.2.4 +++ src/test/org/apache/commons/httpclient/TestHttpConnectionManager.java 11 Mar 2004 03:33:01 -0000 @@ -357,6 +357,94 @@ } /** + * Tests that {@link MultiThreadedHttpConnectionManager#shutdownAll()} closes all resources + * and makes all connection mangers unusable. + */ + public void testShutdownAll() { + + MultiThreadedHttpConnectionManager connectionManager = new MultiThreadedHttpConnectionManager(); + connectionManager.setMaxConnectionsPerHost(1); + connectionManager.setMaxTotalConnections(1); + + HostConfiguration host1 = new HostConfiguration(); + host1.setHost("host1", -1, "http"); + + // hold on to the only connection + HttpConnection connection = connectionManager.getConnection(host1); + + // wait for a connection on another thread + GetConnectionThread getConn = new GetConnectionThread(host1, connectionManager, 0); + getConn.start(); + + MultiThreadedHttpConnectionManager.shutdownAll(); + + // now release this connection, this should close the connection, but have no other effect + connection.releaseConnection(); + connection = null; + + try { + getConn.join(); + } catch (InterruptedException e) { + e.printStackTrace(); + } + + // this thread should have caught an exception without getting a connection + assertNull("Not connection should have been checked out", getConn.getConnection()); + assertNotNull("There should have been an exception", getConn.getException()); + + try { + connectionManager.getConnection(host1); + fail("An exception should have occurred"); + } catch (Exception e) { + // this is expected + } + } + + /** + * Tests that {@link MultiThreadedHttpConnectionManager#shutdown()} closes all resources + * and makes the connection manger unusable. + */ + public void testShutdown() { + + MultiThreadedHttpConnectionManager connectionManager = new MultiThreadedHttpConnectionManager(); + connectionManager.setMaxConnectionsPerHost(1); + connectionManager.setMaxTotalConnections(1); + + HostConfiguration host1 = new HostConfiguration(); + host1.setHost("host1", -1, "http"); + + // hold on to the only connection + HttpConnection connection = connectionManager.getConnection(host1); + + // wait for a connection on another thread + GetConnectionThread getConn = new GetConnectionThread(host1, connectionManager, 0); + getConn.start(); + + connectionManager.shutdown(); + + // now release this connection, this should close the connection, but have no other effect + connection.releaseConnection(); + connection = null; + + try { + getConn.join(); + } catch (InterruptedException e) { + e.printStackTrace(); + } + + // this thread should have caught an exception without getting a connection + assertNull("Not connection should have been checked out", getConn.getConnection()); + assertNotNull("There should have been an exception", getConn.getException()); + + try { + connectionManager.getConnection(host1); + fail("An exception should have occurred"); + } catch (Exception e) { + // this is expected + } + } + + /** * Tests the MultiThreadedHttpConnectionManager's ability to restrict the maximum number * of connections. */ @@ -562,6 +650,7 @@ private MultiThreadedHttpConnectionManager connectionManager; private HttpConnection connection; private long timeout; + private Exception exception; public GetConnectionThread( HostConfiguration hostConfiguration, @@ -576,8 +665,13 @@ public void run() { try { connection = connectionManager.getConnection(hostConfiguration, timeout); - } catch (HttpException e) { + } catch (Exception e) { + exception = e; } + } + + public Exception getException() { + return exception; } public HttpConnection getConnection() {