mbecke 2004/03/28 13:06:24 Modified: httpclient/src/java/org/apache/commons/httpclient Tag: HTTPCLIENT_2_0_BRANCH MultiThreadedHttpConnectionManager.java httpclient/src/test/org/apache/commons/httpclient Tag: HTTPCLIENT_2_0_BRANCH TestHttpConnectionManager.java Log: Added MultiThreadedHttpConnectionManager shutdown() and shutdownAll(). PR: 27589 Submitted by: Michael Becke Reviewed by: Oleg Kalnichevski Revision Changes Path No revision No revision 1.17.2.8 +169 -15 jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/MultiThreadedHttpConnectionManager.java Index: MultiThreadedHttpConnectionManager.java =================================================================== RCS file: /home/cvs/jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/MultiThreadedHttpConnectionManager.java,v retrieving revision 1.17.2.7 retrieving revision 1.17.2.8 diff -u -r1.17.2.7 -r1.17.2.8 --- MultiThreadedHttpConnectionManager.java 22 Feb 2004 18:21:13 -0000 1.17.2.7 +++ MultiThreadedHttpConnectionManager.java 28 Mar 2004 21:06:24 -0000 1.17.2.8 @@ -39,11 +39,12 @@ import java.lang.ref.WeakReference; import java.net.InetAddress; import java.net.SocketException; -import java.util.Collections; +import java.util.ArrayList; 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 +76,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 +89,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 [EMAIL PROTECTED] #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 +152,57 @@ 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 + ); + } + } + + /** + * Closes and releases all connections currently checked out of the given connection pool. + * @param connectionPool the connection pool to shutdown the connections for + */ + private static void shutdownCheckedOutConnections(ConnectionPool connectionPool) { + + // keep a list of the connections to be closed + ArrayList connectionsToClose = new ArrayList(); + + synchronized (REFERENCE_TO_CONNECTION_SOURCE) { + + Iterator referenceIter = REFERENCE_TO_CONNECTION_SOURCE.keySet().iterator(); + while (referenceIter.hasNext()) { + Reference ref = (Reference) referenceIter.next(); + ConnectionSource source = + (ConnectionSource) REFERENCE_TO_CONNECTION_SOURCE.get(ref); + if (source.connectionPool == connectionPool) { + referenceIter.remove(); + HttpConnection connection = (HttpConnection) ref.get(); + if (connection != null) { + connectionsToClose.add(connection); + } + } + } + } + + // close and release the connections outside of the synchronized block to + // avoid holding the lock for too long + for (Iterator i = connectionsToClose.iterator(); i.hasNext();) { + HttpConnection connection = (HttpConnection) i.next(); + connection.close(); + // remove the reference to the connection manager. this ensures + // that the we don't accidentally end up here again + connection.setHttpConnectionManager(null); + connection.releaseConnection(); + } } /** @@ -151,6 +230,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 +240,29 @@ */ 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. All connections associated + * with this class will be closed and released. + * + * <p>The connection manager can no longer be used once shutdown. + * + * <p>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 <code>true</code> if stale checking will be enabled on HttpConections @@ -303,6 +404,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 +573,34 @@ 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(); + } + + // close all connections that have been checked out + shutdownCheckedOutConnections(this); + + // 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 +792,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 +876,8 @@ */ private static class ReferenceQueueThread extends Thread { + private boolean shutdown = false; + /** * Create an instance and make this a daemon thread. */ @@ -743,6 +886,10 @@ setName("MultiThreadedHttpConnectionManager cleanup"); } + public void shutdown() { + this.shutdown = true; + } + /** * Handles cleaning up for the given connection reference. * @@ -750,7 +897,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 +919,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); } No revision No revision 1.8.2.5 +99 -5 jakarta-commons/httpclient/src/test/org/apache/commons/httpclient/TestHttpConnectionManager.java Index: TestHttpConnectionManager.java =================================================================== RCS file: /home/cvs/jakarta-commons/httpclient/src/test/org/apache/commons/httpclient/TestHttpConnectionManager.java,v retrieving revision 1.8.2.4 retrieving revision 1.8.2.5 diff -u -r1.8.2.4 -r1.8.2.5 --- TestHttpConnectionManager.java 22 Feb 2004 18:21:16 -0000 1.8.2.4 +++ TestHttpConnectionManager.java 28 Mar 2004 21:06:24 -0000 1.8.2.5 @@ -357,6 +357,94 @@ } /** + * Tests that [EMAIL PROTECTED] 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 [EMAIL PROTECTED] 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() {
--------------------------------------------------------------------- To unsubscribe, e-mail: [EMAIL PROTECTED] For additional commands, e-mail: [EMAIL PROTECTED]