KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > apache > commons > httpclient > MultiThreadedHttpConnectionManager


1 /*
2  * $Header: /home/cvs/jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/MultiThreadedHttpConnectionManager.java,v 1.17.2.8 2004/03/28 21:06:24 mbecke Exp $
3  * $Revision: 1.17.2.8 $
4  * $Date: 2004/03/28 21:06:24 $
5  *
6  * ====================================================================
7  *
8  * Copyright 2002-2004 The Apache Software Foundation
9  *
10  * Licensed under the Apache License, Version 2.0 (the "License");
11  * you may not use this file except in compliance with the License.
12  * You may obtain a copy of the License at
13  *
14  * http://www.apache.org/licenses/LICENSE-2.0
15  *
16  * Unless required by applicable law or agreed to in writing, software
17  * distributed under the License is distributed on an "AS IS" BASIS,
18  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
19  * See the License for the specific language governing permissions and
20  * limitations under the License.
21  * ====================================================================
22  *
23  * This software consists of voluntary contributions made by many
24  * individuals on behalf of the Apache Software Foundation. For more
25  * information on the Apache Software Foundation, please see
26  * <http://www.apache.org/>.
27  *
28  * [Additional notices, if required by prior licensing conditions]
29  *
30  */

31
32 package org.apache.commons.httpclient;
33
34 import java.io.IOException JavaDoc;
35 import java.io.InputStream JavaDoc;
36 import java.io.OutputStream JavaDoc;
37 import java.lang.ref.Reference JavaDoc;
38 import java.lang.ref.ReferenceQueue JavaDoc;
39 import java.lang.ref.WeakReference JavaDoc;
40 import java.net.InetAddress JavaDoc;
41 import java.net.SocketException JavaDoc;
42 import java.util.ArrayList JavaDoc;
43 import java.util.HashMap JavaDoc;
44 import java.util.Iterator JavaDoc;
45 import java.util.LinkedList JavaDoc;
46 import java.util.Map JavaDoc;
47 import java.util.WeakHashMap JavaDoc;
48
49 import org.apache.commons.httpclient.protocol.Protocol;
50 import org.apache.commons.logging.Log;
51 import org.apache.commons.logging.LogFactory;
52
53 /**
54  * Manages a set of HttpConnections for various HostConfigurations.
55  *
56  * @author <a HREF="mailto:becke@u.washington.edu">Michael Becke</a>
57  * @author Eric Johnson
58  * @author <a HREF="mailto:mbowler@GargoyleSoftware.com">Mike Bowler</a>
59  * @author Carl A. Dunham
60  *
61  * @since 2.0
62  */

63 public class MultiThreadedHttpConnectionManager implements HttpConnectionManager {
64
65     // -------------------------------------------------------- Class Variables
66
/** Log object for this class. */
67     private static final Log LOG = LogFactory.getLog(MultiThreadedHttpConnectionManager.class);
68
69     /** The default maximum number of connections allowed per host */
70     public static final int DEFAULT_MAX_HOST_CONNECTIONS = 2; // Per RFC 2616 sec 8.1.4
71

72     /** The default maximum number of connections allowed overall */
73     public static final int DEFAULT_MAX_TOTAL_CONNECTIONS = 20;
74
75     /**
76      * A mapping from Reference to ConnectionSource. Used to reclaim resources when connections
77      * are lost to the garbage collector.
78      */

79     private static final Map JavaDoc REFERENCE_TO_CONNECTION_SOURCE = new HashMap JavaDoc();
80     
81     /**
82      * The reference queue used to track when HttpConnections are lost to the
83      * garbage collector
84      */

85     private static final ReferenceQueue JavaDoc REFERENCE_QUEUE = new ReferenceQueue JavaDoc();
86
87     /**
88      * The thread responsible for handling lost connections.
89      */

90     private static ReferenceQueueThread REFERENCE_QUEUE_THREAD;
91     
92     /**
93      * Holds references to all active instances of this class.
94      */

95     private static WeakHashMap JavaDoc ALL_CONNECTION_MANAGERS = new WeakHashMap JavaDoc();
96     
97     /**
98      * Shuts down and cleans up resources used by all instances of
99      * MultiThreadedHttpConnectionManager. All static resources are released, all threads are
100      * stopped, and {@link #shutdown()} is called on all live instaces of
101      * MultiThreadedHttpConnectionManager.
102      *
103      * @see #shutdown()
104      */

105     public static void shutdownAll() {
106
107         synchronized (REFERENCE_TO_CONNECTION_SOURCE) {
108             // shutdown all connection managers
109
synchronized (ALL_CONNECTION_MANAGERS) {
110                 Iterator JavaDoc connIter = ALL_CONNECTION_MANAGERS.keySet().iterator();
111                 while (connIter.hasNext()) {
112                     MultiThreadedHttpConnectionManager connManager =
113                         (MultiThreadedHttpConnectionManager) connIter.next();
114                     connIter.remove();
115                     connManager.shutdown();
116                 }
117             }
118             
119             // shutdown static resources
120
if (REFERENCE_QUEUE_THREAD != null) {
121                 REFERENCE_QUEUE_THREAD.shutdown();
122                 REFERENCE_QUEUE_THREAD = null;
123             }
124             REFERENCE_TO_CONNECTION_SOURCE.clear();
125         }
126     }
127     
128     /**
129      * Stores the reference to the given connection along with the hostConfig and connection pool.
130      * These values will be used to reclaim resources if the connection is lost to the garbage
131      * collector. This method should be called before a connection is released from the connection
132      * manager.
133      *
134      * <p>A static reference to the connection manager will also be stored. To ensure that
135      * the connection manager can be GCed {@link #removeReferenceToConnection(HttpConnection)}
136      * should be called for all connections that the connection manager is storing a reference
137      * to.</p>
138      *
139      * @param connection the connection to create a reference for
140      * @param hostConfiguration the connection's host config
141      * @param connectionPool the connection pool that created the connection
142      *
143      * @see #removeReferenceToConnection(HttpConnection)
144      */

145     private static void storeReferenceToConnection(
146         HttpConnectionWithReference connection,
147         HostConfiguration hostConfiguration,
148         ConnectionPool connectionPool
149     ) {
150         
151         ConnectionSource source = new ConnectionSource();
152         source.connectionPool = connectionPool;
153         source.hostConfiguration = hostConfiguration;
154         
155         synchronized (REFERENCE_TO_CONNECTION_SOURCE) {
156             
157             // start the reference queue thread if needed
158
if (REFERENCE_QUEUE_THREAD == null) {
159                 REFERENCE_QUEUE_THREAD = new ReferenceQueueThread();
160                 REFERENCE_QUEUE_THREAD.start();
161             }
162             
163             REFERENCE_TO_CONNECTION_SOURCE.put(
164                 connection.reference,
165                 source
166             );
167         }
168     }
169     
170     /**
171      * Closes and releases all connections currently checked out of the given connection pool.
172      * @param connectionPool the connection pool to shutdown the connections for
173      */

174     private static void shutdownCheckedOutConnections(ConnectionPool connectionPool) {
175
176         // keep a list of the connections to be closed
177
ArrayList JavaDoc connectionsToClose = new ArrayList JavaDoc();
178         
179         synchronized (REFERENCE_TO_CONNECTION_SOURCE) {
180             
181             Iterator JavaDoc referenceIter = REFERENCE_TO_CONNECTION_SOURCE.keySet().iterator();
182             while (referenceIter.hasNext()) {
183                 Reference JavaDoc ref = (Reference JavaDoc) referenceIter.next();
184                 ConnectionSource source =
185                     (ConnectionSource) REFERENCE_TO_CONNECTION_SOURCE.get(ref);
186                 if (source.connectionPool == connectionPool) {
187                     referenceIter.remove();
188                     HttpConnection connection = (HttpConnection) ref.get();
189                     if (connection != null) {
190                         connectionsToClose.add(connection);
191                     }
192                 }
193             }
194         }
195
196         // close and release the connections outside of the synchronized block to
197
// avoid holding the lock for too long
198
for (Iterator JavaDoc i = connectionsToClose.iterator(); i.hasNext();) {
199             HttpConnection connection = (HttpConnection) i.next();
200             connection.close();
201             // remove the reference to the connection manager. this ensures
202
// that the we don't accidentally end up here again
203
connection.setHttpConnectionManager(null);
204             connection.releaseConnection();
205         }
206     }
207     
208     /**
209      * Removes the reference being stored for the given connection. This method should be called
210      * when the connection manager again has a direct reference to the connection.
211      *
212      * @param connection the connection to remove the reference for
213      *
214      * @see #storeReferenceToConnection(HttpConnection, HostConfiguration, ConnectionPool)
215      */

216     private static void removeReferenceToConnection(HttpConnectionWithReference connection) {
217         
218         synchronized (REFERENCE_TO_CONNECTION_SOURCE) {
219             REFERENCE_TO_CONNECTION_SOURCE.remove(connection.reference);
220         }
221     }
222     
223     // ----------------------------------------------------- Instance Variables
224
/** Maximum number of connections allowed per host */
225     private int maxHostConnections = DEFAULT_MAX_HOST_CONNECTIONS;
226
227     /** Maximum number of connections allowed overall */
228     private int maxTotalConnections = DEFAULT_MAX_TOTAL_CONNECTIONS;
229
230     /** The value to set when calling setStaleCheckingEnabled() on each connection */
231     private boolean connectionStaleCheckingEnabled = true;
232
233     private boolean shutdown = false;
234     
235     /** Connection Pool */
236     private ConnectionPool connectionPool;
237
238     /**
239      * No-args constructor
240      */

241     public MultiThreadedHttpConnectionManager() {
242         this.connectionPool = new ConnectionPool();
243         synchronized(ALL_CONNECTION_MANAGERS) {
244             ALL_CONNECTION_MANAGERS.put(this, null);
245         }
246     }
247
248     /**
249      * Shuts down the connection manager and releases all resources. All connections associated
250      * with this class will be closed and released.
251      *
252      * <p>The connection manager can no longer be used once shutdown.
253      *
254      * <p>Calling this method more than once will have no effect.
255      */

256     public synchronized void shutdown() {
257         synchronized (connectionPool) {
258             if (!shutdown) {
259                 shutdown = true;
260                 connectionPool.shutdown();
261             }
262         }
263     }
264     
265     /**
266      * Gets the staleCheckingEnabled value to be set on HttpConnections that are created.
267      *
268      * @return <code>true</code> if stale checking will be enabled on HttpConections
269      *
270      * @see HttpConnection#isStaleCheckingEnabled()
271      */

272     public boolean isConnectionStaleCheckingEnabled() {
273         return connectionStaleCheckingEnabled;
274     }
275
276     /**
277      * Sets the staleCheckingEnabled value to be set on HttpConnections that are created.
278      *
279      * @param connectionStaleCheckingEnabled <code>true</code> if stale checking will be enabled
280      * on HttpConections
281      *
282      * @see HttpConnection#setStaleCheckingEnabled(boolean)
283      */

284     public void setConnectionStaleCheckingEnabled(boolean connectionStaleCheckingEnabled) {
285         this.connectionStaleCheckingEnabled = connectionStaleCheckingEnabled;
286     }
287
288     /**
289      * Sets the maximum number of connections allowed for a given
290      * HostConfiguration. Per RFC 2616 section 8.1.4, this value defaults to 2.
291      *
292      * @param maxHostConnections the number of connections allowed for each
293      * hostConfiguration
294      */

295     public void setMaxConnectionsPerHost(int maxHostConnections) {
296         this.maxHostConnections = maxHostConnections;
297     }
298
299     /**
300      * Gets the maximum number of connections allowed for a given
301      * hostConfiguration.
302      *
303      * @return The maximum number of connections allowed for a given
304      * hostConfiguration.
305      */

306     public int getMaxConnectionsPerHost() {
307         return maxHostConnections;
308     }
309
310     /**
311      * Sets the maximum number of connections allowed in the system.
312      *
313      * @param maxTotalConnections the maximum number of connections allowed
314      */

315     public void setMaxTotalConnections(int maxTotalConnections) {
316         this.maxTotalConnections = maxTotalConnections;
317     }
318
319     /**
320      * Gets the maximum number of connections allowed in the system.
321      *
322      * @return The maximum number of connections allowed
323      */

324     public int getMaxTotalConnections() {
325         return maxTotalConnections;
326     }
327
328     /**
329      * @see HttpConnectionManager#getConnection(HostConfiguration)
330      */

331     public HttpConnection getConnection(HostConfiguration hostConfiguration) {
332
333         while (true) {
334             try {
335                 return getConnection(hostConfiguration, 0);
336             } catch (HttpException e) {
337                 // we'll go ahead and log this, but it should never happen. HttpExceptions
338
// are only thrown when the timeout occurs and since we have no timeout
339
// it should never happen.
340
LOG.debug(
341                     "Unexpected exception while waiting for connection",
342                     e
343                 );
344             };
345         }
346     }
347
348     /**
349      * @see HttpConnectionManager#getConnection(HostConfiguration, long)
350      */

351     public HttpConnection getConnection(HostConfiguration hostConfiguration,
352         long timeout) throws HttpException {
353
354         LOG.trace("enter HttpConnectionManager.getConnection(HostConfiguration, long)");
355
356         if (hostConfiguration == null) {
357             throw new IllegalArgumentException JavaDoc("hostConfiguration is null");
358         }
359
360         if (LOG.isDebugEnabled()) {
361             LOG.debug("HttpConnectionManager.getConnection: config = "
362                 + hostConfiguration + ", timeout = " + timeout);
363         }
364
365         final HttpConnection conn = doGetConnection(hostConfiguration, timeout);
366
367         // wrap the connection in an adapter so we can ensure it is used
368
// only once
369
return new HttpConnectionAdapter(conn);
370     }
371
372     /**
373      * Gets a connection or waits if one is not available. A connection is
374      * available if one exists that is not being used or if fewer than
375      * maxHostConnections have been created in the connectionPool, and fewer
376      * than maxTotalConnections have been created in all connectionPools.
377      *
378      * @param hostConfiguration The host configuration.
379      * @param timeout the number of milliseconds to wait for a connection, 0 to
380      * wait indefinitely
381      *
382      * @return HttpConnection an available connection
383      *
384      * @throws HttpException if a connection does not become available in
385      * 'timeout' milliseconds
386      */

387     private HttpConnection doGetConnection(HostConfiguration hostConfiguration,
388         long timeout) throws HttpException {
389
390         HttpConnection connection = null;
391
392         synchronized (connectionPool) {
393
394             // we clone the hostConfiguration
395
// so that it cannot be changed once the connection has been retrieved
396
hostConfiguration = new HostConfiguration(hostConfiguration);
397             HostConnectionPool hostPool = connectionPool.getHostPool(hostConfiguration);
398             WaitingThread waitingThread = null;
399
400             boolean useTimeout = (timeout > 0);
401             long timeToWait = timeout;
402             long startWait = 0;
403             long endWait = 0;
404
405             while (connection == null) {
406
407                 if (shutdown) {
408                     throw new IllegalStateException JavaDoc("Connection factory has been shutdown.");
409                 }
410                 
411                 // happen to have a free connection with the right specs
412
//
413
if (hostPool.freeConnections.size() > 0) {
414                     connection = connectionPool.getFreeConnection(hostConfiguration);
415
416                 // have room to make more
417
//
418
} else if ((hostPool.numConnections < maxHostConnections)
419                     && (connectionPool.numConnections < maxTotalConnections)) {
420
421                     connection = connectionPool.createConnection(hostConfiguration);
422
423                 // have room to add host connection, and there is at least one free
424
// connection that can be liberated to make overall room
425
//
426
} else if ((hostPool.numConnections < maxHostConnections)
427                     && (connectionPool.freeConnections.size() > 0)) {
428
429                     connectionPool.deleteLeastUsedConnection();
430                     connection = connectionPool.createConnection(hostConfiguration);
431
432                 // otherwise, we have to wait for one of the above conditions to
433
// become true
434
//
435
} else {
436                     // TODO: keep track of which hostConfigurations have waiting
437
// threads, so they avoid being sacrificed before necessary
438

439                     try {
440                         
441                         if (useTimeout && timeToWait <= 0) {
442                             throw new HttpException("Timeout waiting for connection");
443                         }
444                         
445                         if (LOG.isDebugEnabled()) {
446                             LOG.debug("Unable to get a connection, waiting..., hostConfig=" + hostConfiguration);
447                         }
448                         
449                         if (waitingThread == null) {
450                             waitingThread = new WaitingThread();
451                             waitingThread.hostConnectionPool = hostPool;
452                             waitingThread.thread = Thread.currentThread();
453                         }
454                                     
455                         if (useTimeout) {
456                             startWait = System.currentTimeMillis();
457                         }
458                         
459                         hostPool.waitingThreads.addLast(waitingThread);
460                         connectionPool.waitingThreads.addLast(waitingThread);
461                         connectionPool.wait(timeToWait);
462                         
463                         // we have not been interrupted so we need to remove ourselves from the
464
// wait queue
465
hostPool.waitingThreads.remove(waitingThread);
466                         connectionPool.waitingThreads.remove(waitingThread);
467                     } catch (InterruptedException JavaDoc e) {
468                         // do nothing
469
} finally {
470                         if (useTimeout) {
471                             endWait = System.currentTimeMillis();
472                             timeToWait -= (endWait - startWait);
473                         }
474                     }
475                 }
476             }
477         }
478         return connection;
479     }
480
481     /**
482      * Gets the number of connections in use for this configuration.
483      *
484      * @param hostConfiguration the key that connections are tracked on
485      * @return the number of connections in use
486      */

487     public int getConnectionsInUse(HostConfiguration hostConfiguration) {
488         synchronized (connectionPool) {
489             HostConnectionPool hostPool = connectionPool.getHostPool(hostConfiguration);
490             return hostPool.numConnections;
491         }
492     }
493
494     /**
495      * Gets the total number of connections in use.
496      *
497      * @return the total number of connections in use
498      */

499     public int getConnectionsInUse() {
500         synchronized (connectionPool) {
501             return connectionPool.numConnections;
502         }
503     }
504
505     /**
506      * Make the given HttpConnection available for use by other requests.
507      * If another thread is blocked in getConnection() that could use this
508      * connection, it will be woken up.
509      *
510      * @param conn the HttpConnection to make available.
511      */

512     public void releaseConnection(HttpConnection conn) {
513         LOG.trace("enter HttpConnectionManager.releaseConnection(HttpConnection)");
514
515         if (conn instanceof HttpConnectionAdapter) {
516             // connections given out are wrapped in an HttpConnectionAdapter
517
conn = ((HttpConnectionAdapter) conn).getWrappedConnection();
518         } else {
519             // this is okay, when an HttpConnectionAdapter is released
520
// is releases the real connection
521
}
522
523         // make sure that the response has been read.
524
SimpleHttpConnectionManager.finishLastResponse(conn);
525
526         connectionPool.freeConnection(conn);
527     }
528
529     /**
530      * Gets the host configuration for a connection.
531      * @param conn the connection to get the configuration of
532      * @return a new HostConfiguration
533      */

534     private HostConfiguration configurationForConnection(HttpConnection conn) {
535
536         HostConfiguration connectionConfiguration = new HostConfiguration();
537         
538         connectionConfiguration.setHost(
539             conn.getHost(),
540             conn.getVirtualHost(),
541             conn.getPort(),
542             conn.getProtocol()
543         );
544         if (conn.getLocalAddress() != null) {
545             connectionConfiguration.setLocalAddress(conn.getLocalAddress());
546         }
547         if (conn.getProxyHost() != null) {
548             connectionConfiguration.setProxy(conn.getProxyHost(), conn.getProxyPort());
549         }
550
551         return connectionConfiguration;
552     }
553     
554
555     /**
556      * Global Connection Pool, including per-host pools
557      */

558     private class ConnectionPool {
559         
560         /** The list of free connections */
561         private LinkedList JavaDoc freeConnections = new LinkedList JavaDoc();
562
563         /** The list of WaitingThreads waiting for a connection */
564         private LinkedList JavaDoc waitingThreads = new LinkedList JavaDoc();
565
566         /**
567          * Map where keys are {@link HostConfiguration}s and values are {@link
568          * HostConnectionPool}s
569          */

570         private final Map JavaDoc mapHosts = new HashMap JavaDoc();
571
572         /** The number of created connections */
573         private int numConnections = 0;
574
575         /**
576          * Cleans up all connection pool resources.
577          */

578         public synchronized void shutdown() {
579             
580             // close all free connections
581
Iterator JavaDoc iter = freeConnections.iterator();
582             while (iter.hasNext()) {
583                 HttpConnection conn = (HttpConnection) iter.next();
584                 iter.remove();
585                 conn.close();
586             }
587             
588             // close all connections that have been checked out
589
shutdownCheckedOutConnections(this);
590             
591             // interrupt all waiting threads
592
iter = waitingThreads.iterator();
593             while (iter.hasNext()) {
594                 WaitingThread waiter = (WaitingThread) iter.next();
595                 iter.remove();
596                 waiter.thread.interrupt();
597             }
598             
599             // clear out map hosts
600
mapHosts.clear();
601         }
602         
603         /**
604          * Creates a new connection and returns is for use of the calling method.
605          *
606          * @param hostConfiguration the configuration for the connection
607          * @return a new connection or <code>null</code> if none are available
608          */

609         public synchronized HttpConnection createConnection(HostConfiguration hostConfiguration) {
610             
611             HttpConnectionWithReference connection = null;
612
613             HostConnectionPool hostPool = getHostPool(hostConfiguration);
614
615             if ((hostPool.numConnections < getMaxConnectionsPerHost())
616                 && (numConnections < getMaxTotalConnections())) {
617
618                 if (LOG.isDebugEnabled()) {
619                     LOG.debug("Allocating new connection, hostConfig=" + hostConfiguration);
620                 }
621                 connection = new HttpConnectionWithReference(hostConfiguration);
622                 connection.setStaleCheckingEnabled(connectionStaleCheckingEnabled);
623                 connection.setHttpConnectionManager(MultiThreadedHttpConnectionManager.this);
624                 numConnections++;
625                 hostPool.numConnections++;
626                 
627                 // store a reference to this connection so that it can be cleaned up
628
// in the event it is not correctly released
629
storeReferenceToConnection(connection, hostConfiguration, this);
630                 
631             } else if (LOG.isDebugEnabled()) {
632                 if (hostPool.numConnections >= getMaxConnectionsPerHost()) {
633                     LOG.debug("No connection allocated, host pool has already reached "
634                         + "maxConnectionsPerHost, hostConfig=" + hostConfiguration
635                         + ", maxConnectionsPerhost=" + getMaxConnectionsPerHost());
636                 } else {
637                     LOG.debug("No connection allocated, maxTotalConnections reached, "
638                         + "maxTotalConnections=" + getMaxTotalConnections());
639                 }
640             }
641             
642             return connection;
643         }
644     
645         /**
646          * Handles cleaning up for a lost connection with the given config. Decrements any
647          * connection counts and notifies waiting threads, if appropriate.
648          *
649          * @param config the host configuration of the connection that was lost
650          */

651         public synchronized void handleLostConnection(HostConfiguration config) {
652             HostConnectionPool hostPool = getHostPool(config);
653             hostPool.numConnections--;
654
655             numConnections--;
656             notifyWaitingThread(config);
657         }
658
659         /**
660          * Get the pool (list) of connections available for the given hostConfig.
661          *
662          * @param hostConfiguration the configuraton for the connection pool
663          * @return a pool (list) of connections available for the given config
664          */

665         public synchronized HostConnectionPool getHostPool(HostConfiguration hostConfiguration) {
666             LOG.trace("enter HttpConnectionManager.ConnectionPool.getHostPool(HostConfiguration)");
667
668             // Look for a list of connections for the given config
669
HostConnectionPool listConnections = (HostConnectionPool)
670                 mapHosts.get(hostConfiguration);
671             if (listConnections == null) {
672                 // First time for this config
673
listConnections = new HostConnectionPool();
674                 listConnections.hostConfiguration = hostConfiguration;
675                 mapHosts.put(hostConfiguration, listConnections);
676             }
677             
678             return listConnections;
679         }
680
681         /**
682          * If available, get a free connection for this host
683          *
684          * @param hostConfiguration the configuraton for the connection pool
685          * @return an available connection for the given config
686          */

687         public synchronized HttpConnection getFreeConnection(HostConfiguration hostConfiguration) {
688
689             HttpConnectionWithReference connection = null;
690             
691             HostConnectionPool hostPool = getHostPool(hostConfiguration);
692
693             if (hostPool.freeConnections.size() > 0) {
694                 connection = (HttpConnectionWithReference) hostPool.freeConnections.removeFirst();
695                 freeConnections.remove(connection);
696                 // store a reference to this connection so that it can be cleaned up
697
// in the event it is not correctly released
698
storeReferenceToConnection(connection, hostConfiguration, this);
699                 if (LOG.isDebugEnabled()) {
700                     LOG.debug("Getting free connection, hostConfig=" + hostConfiguration);
701                 }
702             } else if (LOG.isDebugEnabled()) {
703                 LOG.debug("There were no free connections to get, hostConfig="
704                     + hostConfiguration);
705             }
706             return connection;
707         }
708
709         /**
710          * Close and delete an old, unused connection to make room for a new one.
711          */

712         public synchronized void deleteLeastUsedConnection() {
713
714             HttpConnection connection = (HttpConnection) freeConnections.removeFirst();
715
716             if (connection != null) {
717                 HostConfiguration connectionConfiguration = configurationForConnection(connection);
718
719                 if (LOG.isDebugEnabled()) {
720                     LOG.debug("Reclaiming unused connection, hostConfig="
721                         + connectionConfiguration);
722                 }
723
724                 connection.close();
725
726                 HostConnectionPool hostPool = getHostPool(connectionConfiguration);
727                 
728                 hostPool.freeConnections.remove(connection);
729                 hostPool.numConnections--;
730                 numConnections--;
731             } else if (LOG.isDebugEnabled()) {
732                 LOG.debug("Attempted to reclaim an unused connection but there were none.");
733             }
734         }
735
736         /**
737          * Notifies a waiting thread that a connection for the given configuration is
738          * available.
739          * @param configuration the host config to use for notifying
740          * @see #notifyWaitingThread(HostConnectionPool)
741          */

742         public synchronized void notifyWaitingThread(HostConfiguration configuration) {
743             notifyWaitingThread(getHostPool(configuration));
744         }
745
746         /**
747          * Notifies a waiting thread that a connection for the given configuration is
748          * available. This will wake a thread witing in tis hostPool or if there is not
749          * one a thread in the ConnectionPool will be notified.
750          *
751          * @param hostPool the host pool to use for notifying
752          */

753         public synchronized void notifyWaitingThread(HostConnectionPool hostPool) {
754
755             // find the thread we are going to notify, we want to ensure that each
756
// waiting thread is only interrupted once so we will remove it from
757
// all wait queues before interrupting it
758
WaitingThread waitingThread = null;
759                 
760             if (hostPool.waitingThreads.size() > 0) {
761                 if (LOG.isDebugEnabled()) {
762                     LOG.debug("Notifying thread waiting on host pool, hostConfig="
763                         + hostPool.hostConfiguration);
764                 }
765                 waitingThread = (WaitingThread) hostPool.waitingThreads.removeFirst();
766                 waitingThreads.remove(waitingThread);
767             } else if (waitingThreads.size() > 0) {
768                 if (LOG.isDebugEnabled()) {
769                     LOG.debug("No-one waiting on host pool, notifying next waiting thread.");
770                 }
771                 waitingThread = (WaitingThread) waitingThreads.removeFirst();
772                 waitingThread.hostConnectionPool.waitingThreads.remove(waitingThread);
773             } else if (LOG.isDebugEnabled()) {
774                 LOG.debug("Notifying no-one, there are no waiting threads");
775             }
776                 
777             if (waitingThread != null) {
778                 waitingThread.thread.interrupt();
779             }
780         }
781
782         /**
783          * Marks the given connection as free.
784          * @param conn a connection that is no longer being used
785          */

786         public void freeConnection(HttpConnection conn) {
787
788             HostConfiguration connectionConfiguration = configurationForConnection(conn);
789
790             if (LOG.isDebugEnabled()) {
791                 LOG.debug("Freeing connection, hostConfig=" + connectionConfiguration);
792             }
793
794             synchronized (this) {
795                 
796                 if (shutdown) {
797                     // the connection manager has been shutdown, release the connection's
798
// resources and get out of here
799
conn.close();
800                     return;
801                 }
802                 
803                 HostConnectionPool hostPool = getHostPool(connectionConfiguration);
804
805                 // Put the connect back in the available list and notify a waiter
806
hostPool.freeConnections.add(conn);
807                 if (hostPool.numConnections == 0) {
808                     // for some reason this connection pool didn't already exist
809
LOG.error("Host connection pool not found, hostConfig="
810                               + connectionConfiguration);
811                     hostPool.numConnections = 1;
812                 }
813
814                 freeConnections.add(conn);
815                 // we can remove the reference to this connection as we have control over
816
// it again. this also ensures that the connection manager can be GCed
817
removeReferenceToConnection((HttpConnectionWithReference) conn);
818                 if (numConnections == 0) {
819                     // for some reason this connection pool didn't already exist
820
LOG.error("Host connection pool not found, hostConfig="
821                               + connectionConfiguration);
822                     numConnections = 1;
823                 }
824                 
825                 notifyWaitingThread(hostPool);
826             }
827         }
828     }
829
830     /**
831      * A simple struct-like class to combine the objects needed to release a connection's
832      * resources when claimed by the garbage collector.
833      */

834     private static class ConnectionSource {
835         
836         /** The connection pool that created the connection */
837       &nbs