mbecke 2004/03/28 13:26:50 Modified: httpclient/src/test/org/apache/commons/httpclient TestHttpConnectionManager.java httpclient/src/java/org/apache/commons/httpclient MultiThreadedHttpConnectionManager.java Log: Added MultiThreadedHttpConnectionManager shutdown() and shutdownAll(). PR: 27589 Submitted by: Michael Becke Reviewed by: Oleg Kalnichevski Revision Changes Path 1.19 +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.18 retrieving revision 1.19 diff -u -r1.18 -r1.19 --- TestHttpConnectionManager.java 22 Feb 2004 18:08:49 -0000 1.18 +++ TestHttpConnectionManager.java 28 Mar 2004 21:26:50 -0000 1.19 @@ -366,6 +366,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. */ @@ -599,6 +687,7 @@ private MultiThreadedHttpConnectionManager connectionManager; private HttpConnection connection; private long timeout; + private Exception exception; public GetConnectionThread( HostConfiguration hostConfiguration, @@ -613,8 +702,13 @@ public void run() { try { connection = connectionManager.getConnectionWithTimeout(hostConfiguration, timeout); - } catch (ConnectTimeoutException e) { + } catch (Exception e) { + this.exception = e; } + } + + public Exception getException() { + return exception; } public HttpConnection getConnection() { 1.34 +171 -19 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.33 retrieving revision 1.34 diff -u -r1.33 -r1.34 --- MultiThreadedHttpConnectionManager.java 22 Feb 2004 18:08:46 -0000 1.33 +++ MultiThreadedHttpConnectionManager.java 28 Mar 2004 21:26:50 -0000 1.34 @@ -39,10 +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.params.HttpConnectionManagerParams; import org.apache.commons.httpclient.params.HttpConnectionParams; @@ -76,26 +78,54 @@ * 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; + private static final Map REFERENCE_TO_CONNECTION_SOURCE = new HashMap(); /** * The reference queue used to track when HttpConnections are lost to the * garbage collector */ - private static final ReferenceQueue REFERENCE_QUEUE; + private static final ReferenceQueue REFERENCE_QUEUE = new ReferenceQueue(); /** * The thread responsible for handling lost connections. */ private static ReferenceQueueThread REFERENCE_QUEUE_THREAD; + /** + * 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() { - static { - REFERENCE_TO_CONNECTION_SOURCE = Collections.synchronizedMap(new HashMap()); - REFERENCE_QUEUE = new ReferenceQueue(); - REFERENCE_QUEUE_THREAD = new ReferenceQueueThread(); - REFERENCE_QUEUE_THREAD.start(); - } + 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(); + } + } /** * Stores the reference to the given connection along with the hostConfig and connection pool. @@ -124,10 +154,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(); + } } /** @@ -154,14 +231,36 @@ /** Connection Pool */ private ConnectionPool connectionPool; + private boolean shutdown = false; + /** * No-args constructor */ 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 @@ -352,6 +451,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) { @@ -543,6 +646,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 @@ -734,6 +865,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 @@ -810,6 +949,8 @@ */ private static class ReferenceQueueThread extends Thread { + private boolean shutdown = false; + /** * Create an instance and make this a daemon thread. */ @@ -818,6 +959,10 @@ setName("MultiThreadedHttpConnectionManager cleanup"); } + public void shutdown() { + this.shutdown = true; + } + /** * Handles cleaning up for the given connection reference. * @@ -825,7 +970,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) { @@ -843,9 +992,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); }
--------------------------------------------------------------------- To unsubscribe, e-mail: [EMAIL PROTECTED] For additional commands, e-mail: [EMAIL PROTECTED]