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 24 Mar 2004 03:46:59 -0000 @@ -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 {@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 +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. + * + *
The connection manager 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 +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);
}
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 24 Mar 2004 03:47: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() {