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]

Reply via email to