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]