1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30 package org.apache.commons.httpclient;
31
32 import java.io.IOException;
33 import java.io.InputStream;
34 import java.io.OutputStream;
35 import java.lang.ref.Reference;
36 import java.lang.ref.ReferenceQueue;
37 import java.lang.ref.WeakReference;
38 import java.net.InetAddress;
39 import java.net.SocketException;
40 import java.util.ArrayList;
41 import java.util.HashMap;
42 import java.util.Iterator;
43 import java.util.LinkedList;
44 import java.util.Map;
45 import java.util.WeakHashMap;
46
47 import org.apache.commons.httpclient.params.HttpConnectionManagerParams;
48 import org.apache.commons.httpclient.params.HttpConnectionParams;
49 import org.apache.commons.httpclient.protocol.Protocol;
50 import org.apache.commons.httpclient.util.IdleConnectionHandler;
51 import org.apache.commons.logging.Log;
52 import org.apache.commons.logging.LogFactory;
53
54 /***
55 * Manages a set of HttpConnections for various HostConfigurations.
56 *
57 * @author <a href="mailto:becke@u.washington.edu">Michael Becke</a>
58 * @author Eric Johnson
59 * @author <a href="mailto:mbowler@GargoyleSoftware.com">Mike Bowler</a>
60 * @author Carl A. Dunham
61 *
62 * @since 2.0
63 */
64 public class MultiThreadedHttpConnectionManager implements HttpConnectionManager {
65
66
67 /*** Log object for this class. */
68 private static final Log LOG = LogFactory.getLog(MultiThreadedHttpConnectionManager.class);
69
70 /*** The default maximum number of connections allowed per host */
71 public static final int DEFAULT_MAX_HOST_CONNECTIONS = 2;
72
73 /*** The default maximum number of connections allowed overall */
74 public static final int DEFAULT_MAX_TOTAL_CONNECTIONS = 20;
75
76 /***
77 * A mapping from Reference to ConnectionSource. Used to reclaim resources when connections
78 * are lost to the garbage collector.
79 */
80 private static final Map REFERENCE_TO_CONNECTION_SOURCE = new HashMap();
81
82 /***
83 * The reference queue used to track when HttpConnections are lost to the
84 * garbage collector
85 */
86 private static final ReferenceQueue REFERENCE_QUEUE = new ReferenceQueue();
87
88 /***
89 * The thread responsible for handling lost connections.
90 */
91 private static ReferenceQueueThread REFERENCE_QUEUE_THREAD;
92
93 /***
94 * Holds references to all active instances of this class.
95 */
96 private static WeakHashMap ALL_CONNECTION_MANAGERS = new WeakHashMap();
97
98 /***
99 * Shuts down and cleans up resources used by all instances of
100 * MultiThreadedHttpConnectionManager. All static resources are released, all threads are
101 * stopped, and {@link #shutdown()} is called on all live instaces of
102 * MultiThreadedHttpConnectionManager.
103 *
104 * @see #shutdown()
105 */
106 public static void shutdownAll() {
107
108 synchronized (REFERENCE_TO_CONNECTION_SOURCE) {
109
110 synchronized (ALL_CONNECTION_MANAGERS) {
111 Iterator connIter = ALL_CONNECTION_MANAGERS.keySet().iterator();
112 while (connIter.hasNext()) {
113 MultiThreadedHttpConnectionManager connManager =
114 (MultiThreadedHttpConnectionManager) connIter.next();
115 connIter.remove();
116 connManager.shutdown();
117 }
118 }
119
120
121 if (REFERENCE_QUEUE_THREAD != null) {
122 REFERENCE_QUEUE_THREAD.shutdown();
123 REFERENCE_QUEUE_THREAD = null;
124 }
125 REFERENCE_TO_CONNECTION_SOURCE.clear();
126 }
127 }
128
129 /***
130 * Stores the reference to the given connection along with the hostConfig and connection pool.
131 * These values will be used to reclaim resources if the connection is lost to the garbage
132 * collector. This method should be called before a connection is released from the connection
133 * manager.
134 *
135 * <p>A static reference to the connection manager will also be stored. To ensure that
136 * the connection manager can be GCed {@link #removeReferenceToConnection(HttpConnection)}
137 * should be called for all connections that the connection manager is storing a reference
138 * to.</p>
139 *
140 * @param connection the connection to create a reference for
141 * @param hostConfiguration the connection's host config
142 * @param connectionPool the connection pool that created the connection
143 *
144 * @see #removeReferenceToConnection(HttpConnection)
145 */
146 private static void storeReferenceToConnection(
147 HttpConnectionWithReference connection,
148 HostConfiguration hostConfiguration,
149 ConnectionPool connectionPool
150 ) {
151
152 ConnectionSource source = new ConnectionSource();
153 source.connectionPool = connectionPool;
154 source.hostConfiguration = hostConfiguration;
155
156 synchronized (REFERENCE_TO_CONNECTION_SOURCE) {
157
158
159 if (REFERENCE_QUEUE_THREAD == null) {
160 REFERENCE_QUEUE_THREAD = new ReferenceQueueThread();
161 REFERENCE_QUEUE_THREAD.start();
162 }
163
164 REFERENCE_TO_CONNECTION_SOURCE.put(
165 connection.reference,
166 source
167 );
168 }
169 }
170
171 /***
172 * Closes and releases all connections currently checked out of the given connection pool.
173 * @param connectionPool the connection pool to shutdown the connections for
174 */
175 private static void shutdownCheckedOutConnections(ConnectionPool connectionPool) {
176
177
178 ArrayList connectionsToClose = new ArrayList();
179
180 synchronized (REFERENCE_TO_CONNECTION_SOURCE) {
181
182 Iterator referenceIter = REFERENCE_TO_CONNECTION_SOURCE.keySet().iterator();
183 while (referenceIter.hasNext()) {
184 Reference ref = (Reference) referenceIter.next();
185 ConnectionSource source =
186 (ConnectionSource) REFERENCE_TO_CONNECTION_SOURCE.get(ref);
187 if (source.connectionPool == connectionPool) {
188 referenceIter.remove();
189 HttpConnection connection = (HttpConnection) ref.get();
190 if (connection != null) {
191 connectionsToClose.add(connection);
192 }
193 }
194 }
195 }
196
197
198
199 for (Iterator i = connectionsToClose.iterator(); i.hasNext();) {
200 HttpConnection connection = (HttpConnection) i.next();
201 connection.close();
202
203
204 connection.setHttpConnectionManager(null);
205 connection.releaseConnection();
206 }
207 }
208
209 /***
210 * Removes the reference being stored for the given connection. This method should be called
211 * when the connection manager again has a direct reference to the connection.
212 *
213 * @param connection the connection to remove the reference for
214 *
215 * @see #storeReferenceToConnection(HttpConnection, HostConfiguration, ConnectionPool)
216 */
217 private static void removeReferenceToConnection(HttpConnectionWithReference connection) {
218
219 synchronized (REFERENCE_TO_CONNECTION_SOURCE) {
220 REFERENCE_TO_CONNECTION_SOURCE.remove(connection.reference);
221 }
222 }
223
224
225 /***
226 * Collection of parameters associated with this connection manager.
227 */
228 private HttpConnectionManagerParams params = new HttpConnectionManagerParams();
229
230 /*** Connection Pool */
231 private ConnectionPool connectionPool;
232
233 private boolean shutdown = false;
234
235 /***
236 * No-args constructor
237 */
238 public MultiThreadedHttpConnectionManager() {
239 this.connectionPool = new ConnectionPool();
240 synchronized(ALL_CONNECTION_MANAGERS) {
241 ALL_CONNECTION_MANAGERS.put(this, null);
242 }
243 }
244
245 /***
246 * Shuts down the connection manager and releases all resources. All connections associated
247 * with this class will be closed and released.
248 *
249 * <p>The connection manager can no longer be used once shutdown.
250 *
251 * <p>Calling this method more than once will have no effect.
252 */
253 public synchronized void shutdown() {
254 synchronized (connectionPool) {
255 if (!shutdown) {
256 shutdown = true;
257 connectionPool.shutdown();
258 }
259 }
260 }
261
262 /***
263 * Gets the staleCheckingEnabled value to be set on HttpConnections that are created.
264 *
265 * @return <code>true</code> if stale checking will be enabled on HttpConections
266 *
267 * @see HttpConnection#isStaleCheckingEnabled()
268 *
269 * @deprecated Use {@link HttpConnectionManagerParams#isStaleCheckingEnabled()},
270 * {@link HttpConnectionManager#getParams()}.
271 */
272 public boolean isConnectionStaleCheckingEnabled() {
273 return this.params.isStaleCheckingEnabled();
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 * @deprecated Use {@link HttpConnectionManagerParams#setStaleCheckingEnabled(boolean)},
285 * {@link HttpConnectionManager#getParams()}.
286 */
287 public void setConnectionStaleCheckingEnabled(boolean connectionStaleCheckingEnabled) {
288 this.params.setStaleCheckingEnabled(connectionStaleCheckingEnabled);
289 }
290
291 /***
292 * Sets the maximum number of connections allowed for a given
293 * HostConfiguration. Per RFC 2616 section 8.1.4, this value defaults to 2.
294 *
295 * @param maxHostConnections the number of connections allowed for each
296 * hostConfiguration
297 *
298 * @deprecated Use {@link HttpConnectionManagerParams#MAX_HOST_CONNECTIONS},
299 * {@link HttpConnectionManager#getParams()}.
300 */
301 public void setMaxConnectionsPerHost(int maxHostConnections) {
302 this.params.setIntParameter(
303 HttpConnectionManagerParams.MAX_HOST_CONNECTIONS,
304 maxHostConnections);
305 }
306
307 /***
308 * Gets the maximum number of connections allowed for a given
309 * hostConfiguration.
310 *
311 * @return The maximum number of connections allowed for a given
312 * hostConfiguration.
313 *
314 * @deprecated Use {@link HttpConnectionManagerParams#MAX_HOST_CONNECTIONS},
315 * {@link HttpConnectionManager#getParams()}.
316 */
317 public int getMaxConnectionsPerHost() {
318 return this.params.getIntParameter(
319 HttpConnectionManagerParams.MAX_HOST_CONNECTIONS,
320 DEFAULT_MAX_HOST_CONNECTIONS);
321 }
322
323 /***
324 * Sets the maximum number of connections allowed in the system.
325 *
326 * @param maxTotalConnections the maximum number of connections allowed
327 *
328 * @deprecated Use {@link HttpConnectionManagerParams#MAX_TOTAL_CONNECTIONS},
329 * {@link HttpConnectionManager#getParams()}.
330 */
331 public void setMaxTotalConnections(int maxTotalConnections) {
332 this.params.setIntParameter(
333 HttpConnectionManagerParams.MAX_TOTAL_CONNECTIONS,
334 maxTotalConnections);
335 }
336
337 /***
338 * Gets the maximum number of connections allowed in the system.
339 *
340 * @return The maximum number of connections allowed
341 *
342 * @deprecated Use {@link HttpConnectionManagerParams#MAX_TOTAL_CONNECTIONS},
343 * {@link HttpConnectionManager#getParams()}.
344 */
345 public int getMaxTotalConnections() {
346 return this.params.getIntParameter(
347 HttpConnectionManagerParams.MAX_TOTAL_CONNECTIONS,
348 DEFAULT_MAX_TOTAL_CONNECTIONS);
349 }
350
351 /***
352 * @see HttpConnectionManager#getConnection(HostConfiguration)
353 */
354 public HttpConnection getConnection(HostConfiguration hostConfiguration) {
355
356 while (true) {
357 try {
358 return getConnectionWithTimeout(hostConfiguration, 0);
359 } catch (ConnectTimeoutException e) {
360
361
362
363 LOG.debug(
364 "Unexpected exception while waiting for connection",
365 e
366 );
367 }
368 }
369 }
370
371 /***
372 * @see HttpConnectionManager#getConnectionWithTimeout(HostConfiguration, long)
373 *
374 * @since 3.0
375 */
376 public HttpConnection getConnectionWithTimeout(HostConfiguration hostConfiguration,
377 long timeout) throws ConnectTimeoutException {
378
379 LOG.trace("enter HttpConnectionManager.getConnectionWithTimeout(HostConfiguration, long)");
380
381 if (hostConfiguration == null) {
382 throw new IllegalArgumentException("hostConfiguration is null");
383 }
384
385 if (LOG.isDebugEnabled()) {
386 LOG.debug("HttpConnectionManager.getConnection: config = "
387 + hostConfiguration + ", timeout = " + timeout);
388 }
389
390 final HttpConnection conn = doGetConnection(hostConfiguration, timeout);
391
392
393
394 return new HttpConnectionAdapter(conn);
395 }
396
397 /***
398 * @see HttpConnectionManager#getConnection(HostConfiguration, long)
399 *
400 * @deprecated Use #getConnectionWithTimeout(HostConfiguration, long)
401 */
402 public HttpConnection getConnection(HostConfiguration hostConfiguration,
403 long timeout) throws HttpException {
404
405 LOG.trace("enter HttpConnectionManager.getConnection(HostConfiguration, long)");
406 try {
407 return getConnectionWithTimeout(hostConfiguration, timeout);
408 } catch(ConnectTimeoutException e) {
409 throw new HttpException(e.getMessage());
410 }
411 }
412
413 /***
414 * Gets a connection or waits if one is not available. A connection is
415 * available if one exists that is not being used or if fewer than
416 * maxHostConnections have been created in the connectionPool, and fewer
417 * than maxTotalConnections have been created in all connectionPools.
418 *
419 * @param hostConfiguration The host configuration.
420 * @param timeout the number of milliseconds to wait for a connection, 0 to
421 * wait indefinitely
422 *
423 * @return HttpConnection an available connection
424 *
425 * @throws HttpException if a connection does not become available in
426 * 'timeout' milliseconds
427 */
428 private HttpConnection doGetConnection(HostConfiguration hostConfiguration,
429 long timeout) throws ConnectTimeoutException {
430
431 HttpConnection connection = null;
432
433 int maxHostConnections = this.params.getIntParameter(
434 HttpConnectionManagerParams.MAX_HOST_CONNECTIONS,
435 DEFAULT_MAX_HOST_CONNECTIONS);
436 int maxTotalConnections = this.params.getIntParameter(
437 HttpConnectionManagerParams.MAX_TOTAL_CONNECTIONS,
438 DEFAULT_MAX_TOTAL_CONNECTIONS);
439
440 synchronized (connectionPool) {
441
442
443
444 hostConfiguration = new HostConfiguration(hostConfiguration);
445 HostConnectionPool hostPool = connectionPool.getHostPool(hostConfiguration);
446 WaitingThread waitingThread = null;
447
448 boolean useTimeout = (timeout > 0);
449 long timeToWait = timeout;
450 long startWait = 0;
451 long endWait = 0;
452
453 while (connection == null) {
454
455 if (shutdown) {
456 throw new IllegalStateException("Connection factory has been shutdown.");
457 }
458
459
460
461 if (hostPool.freeConnections.size() > 0) {
462 connection = connectionPool.getFreeConnection(hostConfiguration);
463
464
465
466 } else if ((hostPool.numConnections < maxHostConnections)
467 && (connectionPool.numConnections < maxTotalConnections)) {
468
469 connection = connectionPool.createConnection(hostConfiguration);
470
471
472
473
474 } else if ((hostPool.numConnections < maxHostConnections)
475 && (connectionPool.freeConnections.size() > 0)) {
476
477 connectionPool.deleteLeastUsedConnection();
478 connection = connectionPool.createConnection(hostConfiguration);
479
480
481
482
483 } else {
484
485
486
487 try {
488
489 if (useTimeout && timeToWait <= 0) {
490 throw new ConnectTimeoutException("Timeout waiting for connection");
491 }
492
493 if (LOG.isDebugEnabled()) {
494 LOG.debug("Unable to get a connection, waiting..., hostConfig=" + hostConfiguration);
495 }
496
497 if (waitingThread == null) {
498 waitingThread = new WaitingThread();
499 waitingThread.hostConnectionPool = hostPool;
500 waitingThread.thread = Thread.currentThread();
501 }
502
503 if (useTimeout) {
504 startWait = System.currentTimeMillis();
505 }
506
507 hostPool.waitingThreads.addLast(waitingThread);
508 connectionPool.waitingThreads.addLast(waitingThread);
509 connectionPool.wait(timeToWait);
510
511
512
513 hostPool.waitingThreads.remove(waitingThread);
514 connectionPool.waitingThreads.remove(waitingThread);
515 } catch (InterruptedException e) {
516
517 } finally {
518 if (useTimeout) {
519 endWait = System.currentTimeMillis();
520 timeToWait -= (endWait - startWait);
521 }
522 }
523 }
524 }
525 }
526 return connection;
527 }
528
529 /***
530 * Gets the number of connections in use for this configuration.
531 *
532 * @param hostConfiguration the key that connections are tracked on
533 * @return the number of connections in use
534 */
535 public int getConnectionsInUse(HostConfiguration hostConfiguration) {
536 synchronized (connectionPool) {
537 HostConnectionPool hostPool = connectionPool.getHostPool(hostConfiguration);
538 return hostPool.numConnections;
539 }
540 }
541
542 /***
543 * Gets the total number of connections in use.
544 *
545 * @return the total number of connections in use
546 */
547 public int getConnectionsInUse() {
548 synchronized (connectionPool) {
549 return connectionPool.numConnections;
550 }
551 }
552
553 /***
554 * @since 3.0
555 */
556 public void closeIdleConnections(long idleTimeout) {
557 connectionPool.closeIdleConnections(idleTimeout);
558 }
559
560 /***
561 * Make the given HttpConnection available for use by other requests.
562 * If another thread is blocked in getConnection() that could use this
563 * connection, it will be woken up.
564 *
565 * @param conn the HttpConnection to make available.
566 */
567 public void releaseConnection(HttpConnection conn) {
568 LOG.trace("enter HttpConnectionManager.releaseConnection(HttpConnection)");
569
570 if (conn instanceof HttpConnectionAdapter) {
571
572 conn = ((HttpConnectionAdapter) conn).getWrappedConnection();
573 } else {
574
575
576 }
577
578
579 SimpleHttpConnectionManager.finishLastResponse(conn);
580
581 connectionPool.freeConnection(conn);
582 }
583
584 /***
585 * Gets the host configuration for a connection.
586 * @param conn the connection to get the configuration of
587 * @return a new HostConfiguration
588 */
589 private HostConfiguration configurationForConnection(HttpConnection conn) {
590
591 HostConfiguration connectionConfiguration = new HostConfiguration();
592
593 connectionConfiguration.setHost(
594 conn.getHost(),
595 conn.getVirtualHost(),
596 conn.getPort(),
597 conn.getProtocol()
598 );
599 if (conn.getLocalAddress() != null) {
600 connectionConfiguration.setLocalAddress(conn.getLocalAddress());
601 }
602 if (conn.getProxyHost() != null) {
603 connectionConfiguration.setProxy(conn.getProxyHost(), conn.getProxyPort());
604 }
605
606 return connectionConfiguration;
607 }
608
609 /***
610 * Returns {@link HttpConnectionManagerParams parameters} associated
611 * with this connection manager.
612 *
613 * @since 3.0
614 *
615 * @see HttpConnectionManagerParams
616 */
617 public HttpConnectionManagerParams getParams() {
618 return this.params;
619 }
620
621 /***
622 * Assigns {@link HttpConnectionManagerParams parameters} for this
623 * connection manager.
624 *
625 * @since 3.0
626 *
627 * @see HttpConnectionManagerParams
628 */
629 public void setParams(final HttpConnectionManagerParams params) {
630 if (params == null) {
631 throw new IllegalArgumentException("Parameters may not be null");
632 }
633 this.params = params;
634 }
635
636 /***
637 * Global Connection Pool, including per-host pools
638 */
639 private class ConnectionPool {
640
641 /*** The list of free connections */
642 private LinkedList freeConnections = new LinkedList();
643
644 /*** The list of WaitingThreads waiting for a connection */
645 private LinkedList waitingThreads = new LinkedList();
646
647 /***
648 * Map where keys are {@link HostConfiguration}s and values are {@link
649 * HostConnectionPool}s
650 */
651 private final Map mapHosts = new HashMap();
652
653 private IdleConnectionHandler idleConnectionHandler = new IdleConnectionHandler();
654
655 /*** The number of created connections */
656 private int numConnections = 0;
657
658 /***
659 * Cleans up all connection pool resources.
660 */
661 public synchronized void shutdown() {
662
663
664 Iterator iter = freeConnections.iterator();
665 while (iter.hasNext()) {
666 HttpConnection conn = (HttpConnection) iter.next();
667 iter.remove();
668 conn.close();
669 }
670
671
672 shutdownCheckedOutConnections(this);
673
674
675 iter = waitingThreads.iterator();
676 while (iter.hasNext()) {
677 WaitingThread waiter = (WaitingThread) iter.next();
678 iter.remove();
679 waiter.thread.interrupt();
680 }
681
682
683 mapHosts.clear();
684
685
686 idleConnectionHandler.removeAll();
687 }
688
689 /***
690 * Creates a new connection and returns is for use of the calling method.
691 *
692 * @param hostConfiguration the configuration for the connection
693 * @return a new connection or <code>null</code> if none are available
694 */
695 public synchronized HttpConnection createConnection(HostConfiguration hostConfiguration) {
696
697 HttpConnectionWithReference connection = null;
698
699 HostConnectionPool hostPool = getHostPool(hostConfiguration);
700
701 if ((hostPool.numConnections < getMaxConnectionsPerHost())
702 && (numConnections < getMaxTotalConnections())) {
703
704 if (LOG.isDebugEnabled()) {
705 LOG.debug("Allocating new connection, hostConfig=" + hostConfiguration);
706 }
707 connection = new HttpConnectionWithReference(hostConfiguration);
708 connection.getParams().setDefaults(MultiThreadedHttpConnectionManager.this.params);
709 connection.setHttpConnectionManager(MultiThreadedHttpConnectionManager.this);
710 numConnections++;
711 hostPool.numConnections++;
712
713
714
715 storeReferenceToConnection(connection, hostConfiguration, this);
716
717 } else if (LOG.isDebugEnabled()) {
718 if (hostPool.numConnections >= getMaxConnectionsPerHost()) {
719 LOG.debug("No connection allocated, host pool has already reached "
720 + "maxConnectionsPerHost, hostConfig=" + hostConfiguration
721 + ", maxConnectionsPerhost=" + getMaxConnectionsPerHost());
722 } else {
723 LOG.debug("No connection allocated, maxTotalConnections reached, "
724 + "maxTotalConnections=" + getMaxTotalConnections());
725 }
726 }
727
728 return connection;
729 }
730
731 /***
732 * Handles cleaning up for a lost connection with the given config. Decrements any
733 * connection counts and notifies waiting threads, if appropriate.
734 *
735 * @param config the host configuration of the connection that was lost
736 */
737 public synchronized void handleLostConnection(HostConfiguration config) {
738 HostConnectionPool hostPool = getHostPool(config);
739 hostPool.numConnections--;
740
741 numConnections--;
742 notifyWaitingThread(config);
743 }
744
745 /***
746 * Get the pool (list) of connections available for the given hostConfig.
747 *
748 * @param hostConfiguration the configuraton for the connection pool
749 * @return a pool (list) of connections available for the given config
750 */
751 public synchronized HostConnectionPool getHostPool(HostConfiguration hostConfiguration) {
752 LOG.trace("enter HttpConnectionManager.ConnectionPool.getHostPool(HostConfiguration)");
753
754
755 HostConnectionPool listConnections = (HostConnectionPool)
756 mapHosts.get(hostConfiguration);
757 if (listConnections == null) {
758
759 listConnections = new HostConnectionPool();
760 listConnections.hostConfiguration = hostConfiguration;
761 mapHosts.put(hostConfiguration, listConnections);
762 }
763
764 return listConnections;
765 }
766
767 /***
768 * If available, get a free connection for this host
769 *
770 * @param hostConfiguration the configuraton for the connection pool
771 * @return an available connection for the given config
772 */
773 public synchronized HttpConnection getFreeConnection(HostConfiguration hostConfiguration) {
774
775 HttpConnectionWithReference connection = null;
776
777 HostConnectionPool hostPool = getHostPool(hostConfiguration);
778
779 if (hostPool.freeConnections.size() > 0) {
780 connection = (HttpConnectionWithReference) hostPool.freeConnections.removeFirst();
781 freeConnections.remove(connection);
782
783
784 storeReferenceToConnection(connection, hostConfiguration, this);
785 if (LOG.isDebugEnabled()) {
786 LOG.debug("Getting free connection, hostConfig=" + hostConfiguration);
787 }
788
789
790 idleConnectionHandler.remove(connection);
791 } else if (LOG.isDebugEnabled()) {
792 LOG.debug("There were no free connections to get, hostConfig="
793 + hostConfiguration);
794 }
795 return connection;
796 }
797
798 /***
799 * Closes idle connections.
800 * @param idleTimeout
801 */
802 public synchronized void closeIdleConnections(long idleTimeout) {
803 idleConnectionHandler.closeIdleConnections(idleTimeout);
804 }
805
806 /***
807 * Close and delete an old, unused connection to make room for a new one.
808 */
809 public synchronized void deleteLeastUsedConnection() {
810
811 HttpConnection connection = (HttpConnection) freeConnections.removeFirst();
812
813 if (connection != null) {
814 HostConfiguration connectionConfiguration = configurationForConnection(connection);
815
816 if (LOG.isDebugEnabled()) {
817 LOG.debug("Reclaiming unused connection, hostConfig="
818 + connectionConfiguration);
819 }
820
821 connection.close();
822
823 HostConnectionPool hostPool = getHostPool(connectionConfiguration);
824
825 hostPool.freeConnections.remove(connection);
826 hostPool.numConnections--;
827 numConnections--;
828
829
830 idleConnectionHandler.remove(connection);
831
832 } else if (LOG.isDebugEnabled()) {
833 LOG.debug("Attempted to reclaim an unused connection but there were none.");
834 }
835 }
836
837 /***
838 * Notifies a waiting thread that a connection for the given configuration is
839 * available.
840 * @param configuration the host config to use for notifying
841 * @see #notifyWaitingThread(HostConnectionPool)
842 */
843 public synchronized void notifyWaitingThread(HostConfiguration configuration) {
844 notifyWaitingThread(getHostPool(configuration));
845 }
846
847 /***
848 * Notifies a waiting thread that a connection for the given configuration is
849 * available. This will wake a thread witing in tis hostPool or if there is not
850 * one a thread in the ConnectionPool will be notified.
851 *
852 * @param hostPool the host pool to use for notifying
853 */
854 public synchronized void notifyWaitingThread(HostConnectionPool hostPool) {
855
856
857
858
859 WaitingThread waitingThread = null;
860
861 if (hostPool.waitingThreads.size() > 0) {
862 if (LOG.isDebugEnabled()) {
863 LOG.debug("Notifying thread waiting on host pool, hostConfig="
864 + hostPool.hostConfiguration);
865 }
866 waitingThread = (WaitingThread) hostPool.waitingThreads.removeFirst();
867 waitingThreads.remove(waitingThread);
868 } else if (waitingThreads.size() > 0) {
869 if (LOG.isDebugEnabled()) {
870 LOG.debug("No-one waiting on host pool, notifying next waiting thread.");
871 }
872 waitingThread = (WaitingThread) waitingThreads.removeFirst();
873 waitingThread.hostConnectionPool.waitingThreads.remove(waitingThread);
874 } else if (LOG.isDebugEnabled()) {
875 LOG.debug("Notifying no-one, there are no waiting threads");
876 }
877
878 if (waitingThread != null) {
879 waitingThread.thread.interrupt();
880 }
881 }
882
883 /***
884 * Marks the given connection as free.
885 * @param conn a connection that is no longer being used
886 */
887 public void freeConnection(HttpConnection conn) {
888
889 HostConfiguration connectionConfiguration = configurationForConnection(conn);
890
891 if (LOG.isDebugEnabled()) {
892 LOG.debug("Freeing connection, hostConfig=" + connectionConfiguration);
893 }
894
895 synchronized (this) {
896
897 if (shutdown) {
898
899
900 conn.close();
901 return;
902 }
903
904 HostConnectionPool hostPool = getHostPool(connectionConfiguration);
905
906
907 hostPool.freeConnections.add(conn);
908 if (hostPool.numConnections == 0) {
909
910 LOG.error("Host connection pool not found, hostConfig="
911 + connectionConfiguration);
912 hostPool.numConnections = 1;
913 }
914
915 freeConnections.add(conn);
916
917
918 removeReferenceToConnection((HttpConnectionWithReference) conn);
919 if (numConnections == 0) {
920
921 LOG.error("Host connection pool not found, hostConfig="
922 + connectionConfiguration);
923 numConnections = 1;
924 }
925
926
927 idleConnectionHandler.add(conn);
928
929 notifyWaitingThread(hostPool);
930 }
931 }
932 }
933
934 /***
935 * A simple struct-like class to combine the objects needed to release a connection's
936 * resources when claimed by the garbage collector.
937 */
938 private static class ConnectionSource {
939
940 /*** The connection pool that created the connection */
941 public ConnectionPool connectionPool;
942
943 /*** The connection's host configuration */
944 public HostConfiguration hostConfiguration;
945 }
946
947 /***
948 * A simple struct-like class to combine the connection list and the count
949 * of created connections.
950 */
951 private static class HostConnectionPool {
952 /*** The hostConfig this pool is for */
953 public HostConfiguration hostConfiguration;
954
955 /*** The list of free connections */
956 public LinkedList freeConnections = new LinkedList();
957
958 /*** The list of WaitingThreads for this host */
959 public LinkedList waitingThreads = new LinkedList();
960
961 /*** The number of created connections */
962 public int numConnections = 0;
963 }
964
965 /***
966 * A simple struct-like class to combine the waiting thread and the connection
967 * pool it is waiting on.
968 */
969 private static class WaitingThread {
970 /*** The thread that is waiting for a connection */
971 public Thread thread;
972
973 /*** The connection pool the thread is waiting for */
974 public HostConnectionPool hostConnectionPool;
975 }
976
977 /***
978 * A thread for listening for HttpConnections reclaimed by the garbage
979 * collector.
980 */
981 private static class ReferenceQueueThread extends Thread {
982
983 private boolean shutdown = false;
984
985 /***
986 * Create an instance and make this a daemon thread.
987 */
988 public ReferenceQueueThread() {
989 setDaemon(true);
990 setName("MultiThreadedHttpConnectionManager cleanup");
991 }
992
993 public void shutdown() {
994 this.shutdown = true;
995 }
996
997 /***
998 * Handles cleaning up for the given connection reference.
999 *
1000 * @param ref the reference to clean up
1001 */
1002 private void handleReference(Reference ref) {
1003
1004 ConnectionSource source = null;
1005
1006 synchronized (REFERENCE_TO_CONNECTION_SOURCE) {
1007 source = (ConnectionSource) REFERENCE_TO_CONNECTION_SOURCE.remove(ref);
1008 }
1009
1010
1011 if (source != null) {
1012 if (LOG.isDebugEnabled()) {
1013 LOG.debug(
1014 "Connection reclaimed by garbage collector, hostConfig="
1015 + source.hostConfiguration);
1016 }
1017
1018 source.connectionPool.handleLostConnection(source.hostConfiguration);
1019 }
1020 }
1021
1022 /***
1023 * Start execution.
1024 */
1025 public void run() {
1026 while (!shutdown) {
1027 try {
1028
1029
1030
1031 Reference ref = REFERENCE_QUEUE.remove(1000);
1032 if (ref != null) {
1033 handleReference(ref);
1034 }
1035 } catch (InterruptedException e) {
1036 LOG.debug("ReferenceQueueThread interrupted", e);
1037 }
1038 }
1039 }
1040
1041 }
1042
1043 /***
1044 * A connection that keeps a reference to itself.
1045 */
1046 private static class HttpConnectionWithReference extends HttpConnection {
1047
1048 public WeakReference reference = new WeakReference(this, REFERENCE_QUEUE);
1049
1050 /***
1051 * @param hostConfiguration
1052 */
1053 public HttpConnectionWithReference(HostConfiguration hostConfiguration) {
1054 super(hostConfiguration);
1055 }
1056
1057 }
1058
1059 /***
1060 * An HttpConnection wrapper that ensures a connection cannot be used
1061 * once released.
1062 */
1063 private static class HttpConnectionAdapter extends HttpConnection {
1064
1065
1066 private HttpConnection wrappedConnection;
1067
1068 /***
1069 * Creates a new HttpConnectionAdapter.
1070 * @param connection the connection to be wrapped
1071 */
1072 public HttpConnectionAdapter(HttpConnection connection) {
1073 super(connection.getHost(), connection.getPort(), connection.getProtocol());
1074 this.wrappedConnection = connection;
1075 }
1076
1077 /***
1078 * Tests if the wrapped connection is still available.
1079 * @return boolean
1080 */
1081 protected boolean hasConnection() {
1082 return wrappedConnection != null;
1083 }
1084
1085 /***
1086 * @return HttpConnection
1087 */
1088 HttpConnection getWrappedConnection() {
1089 return wrappedConnection;
1090 }
1091
1092 public void close() {
1093 if (hasConnection()) {
1094 wrappedConnection.close();
1095 } else {
1096
1097 }
1098 }
1099
1100 public InetAddress getLocalAddress() {
1101 if (hasConnection()) {
1102 return wrappedConnection.getLocalAddress();
1103 } else {
1104 return null;
1105 }
1106 }
1107
1108 /***
1109 * @deprecated
1110 */
1111 public boolean isStaleCheckingEnabled() {
1112 if (hasConnection()) {
1113 return wrappedConnection.isStaleCheckingEnabled();
1114 } else {
1115 return false;
1116 }
1117 }
1118
1119 public void setLocalAddress(InetAddress localAddress) {
1120 if (hasConnection()) {
1121 wrappedConnection.setLocalAddress(localAddress);
1122 } else {
1123 throw new IllegalStateException("Connection has been released");
1124 }
1125 }
1126
1127 /***
1128 * @deprecated
1129 */
1130 public void setStaleCheckingEnabled(boolean staleCheckEnabled) {
1131 if (hasConnection()) {
1132 wrappedConnection.setStaleCheckingEnabled(staleCheckEnabled);
1133 } else {
1134 throw new IllegalStateException("Connection has been released");
1135 }
1136 }
1137
1138 public String getHost() {
1139 if (hasConnection()) {
1140 return wrappedConnection.getHost();
1141 } else {
1142 return null;
1143 }
1144 }
1145
1146 public HttpConnectionManager getHttpConnectionManager() {
1147 if (hasConnection()) {
1148 return wrappedConnection.getHttpConnectionManager();
1149 } else {
1150 return null;
1151 }
1152 }
1153
1154 public InputStream getLastResponseInputStream() {
1155 if (hasConnection()) {
1156 return wrappedConnection.getLastResponseInputStream();
1157 } else {
1158 return null;
1159 }
1160 }
1161
1162 public int getPort() {
1163 if (hasConnection()) {
1164 return wrappedConnection.getPort();
1165 } else {
1166 return -1;
1167 }
1168 }
1169
1170 public Protocol getProtocol() {
1171 if (hasConnection()) {
1172 return wrappedConnection.getProtocol();
1173 } else {
1174 return null;
1175 }
1176 }
1177
1178 public String getProxyHost() {
1179 if (hasConnection()) {
1180 return wrappedConnection.getProxyHost();
1181 } else {
1182 return null;
1183 }
1184 }
1185
1186 public int getProxyPort() {
1187 if (hasConnection()) {
1188 return wrappedConnection.getProxyPort();
1189 } else {
1190 return -1;
1191 }
1192 }
1193
1194 public OutputStream getRequestOutputStream()
1195 throws IOException, IllegalStateException {
1196 if (hasConnection()) {
1197 return wrappedConnection.getRequestOutputStream();
1198 } else {
1199 return null;
1200 }
1201 }
1202
1203 public InputStream getResponseInputStream()
1204 throws IOException, IllegalStateException {
1205 if (hasConnection()) {
1206 return wrappedConnection.getResponseInputStream();
1207 } else {
1208 return null;
1209 }
1210 }
1211
1212 public boolean isOpen() {
1213 if (hasConnection()) {
1214 return wrappedConnection.isOpen();
1215 } else {
1216 return false;
1217 }
1218 }
1219
1220 public boolean isProxied() {
1221 if (hasConnection()) {
1222 return wrappedConnection.isProxied();
1223 } else {
1224 return false;
1225 }
1226 }
1227
1228 public boolean isResponseAvailable() throws IOException {
1229 if (hasConnection()) {
1230 return wrappedConnection.isResponseAvailable();
1231 } else {
1232 return false;
1233 }
1234 }
1235
1236 public boolean isResponseAvailable(int timeout) throws IOException {
1237 if (hasConnection()) {
1238 return wrappedConnection.isResponseAvailable(timeout);
1239 } else {
1240 return false;
1241 }
1242 }
1243
1244 public boolean isSecure() {
1245 if (hasConnection()) {
1246 return wrappedConnection.isSecure();
1247 } else {
1248 return false;
1249 }
1250 }
1251
1252 public boolean isTransparent() {
1253 if (hasConnection()) {
1254 return wrappedConnection.isTransparent();
1255 } else {
1256 return false;
1257 }
1258 }
1259
1260 public void open() throws IOException {
1261 if (hasConnection()) {
1262 wrappedConnection.open();
1263 } else {
1264 throw new IllegalStateException("Connection has been released");
1265 }
1266 }
1267
1268 /***
1269 * @deprecated
1270 */
1271 public void print(String data)
1272 throws IOException, IllegalStateException, HttpRecoverableException {
1273 if (hasConnection()) {
1274 wrappedConnection.print(data);
1275 } else {
1276 throw new IllegalStateException("Connection has been released");
1277 }
1278 }
1279
1280 public void printLine()
1281 throws IOException, IllegalStateException, HttpRecoverableException {
1282 if (hasConnection()) {
1283 wrappedConnection.printLine();
1284 } else {
1285 throw new IllegalStateException("Connection has been released");
1286 }
1287 }
1288
1289 /***
1290 * @deprecated
1291 */
1292 public void printLine(String data)
1293 throws IOException, IllegalStateException, HttpRecoverableException {
1294 if (hasConnection()) {
1295 wrappedConnection.printLine(data);
1296 } else {
1297 throw new IllegalStateException("Connection has been released");
1298 }
1299 }
1300
1301 /***
1302 * @deprecated
1303 */
1304 public String readLine() throws IOException, IllegalStateException {
1305 if (hasConnection()) {
1306 return wrappedConnection.readLine();
1307 } else {
1308 throw new IllegalStateException("Connection has been released");
1309 }
1310 }
1311
1312 public String readLine(String charset) throws IOException, IllegalStateException {
1313 if (hasConnection()) {
1314 return wrappedConnection.readLine(charset);
1315 } else {
1316 throw new IllegalStateException("Connection has been released");
1317 }
1318 }
1319
1320 public void releaseConnection() {
1321 if (!isLocked() && hasConnection()) {
1322 HttpConnection wrappedConnection = this.wrappedConnection;
1323 this.wrappedConnection = null;
1324 wrappedConnection.releaseConnection();
1325 } else {
1326
1327 }
1328 }
1329
1330 /***
1331 * @deprecated
1332 */
1333 public void setConnectionTimeout(int timeout) {
1334 if (hasConnection()) {
1335 wrappedConnection.setConnectionTimeout(timeout);
1336 } else {
1337
1338 }
1339 }
1340
1341 public void setHost(String host) throws IllegalStateException {
1342 if (hasConnection()) {
1343 wrappedConnection.setHost(host);
1344 } else {
1345
1346 }
1347 }
1348
1349 public void setHttpConnectionManager(HttpConnectionManager httpConnectionManager) {
1350 if (hasConnection()) {
1351 wrappedConnection.setHttpConnectionManager(httpConnectionManager);
1352 } else {
1353
1354 }
1355 }
1356
1357 public void setLastResponseInputStream(InputStream inStream) {
1358 if (hasConnection()) {
1359 wrappedConnection.setLastResponseInputStream(inStream);
1360 } else {
1361
1362 }
1363 }
1364
1365 public void setPort(int port) throws IllegalStateException {
1366 if (hasConnection()) {
1367 wrappedConnection.setPort(port);
1368 } else {
1369
1370 }
1371 }
1372
1373 public void setProtocol(Protocol protocol) {
1374 if (hasConnection()) {
1375 wrappedConnection.setProtocol(protocol);
1376 } else {
1377
1378 }
1379 }
1380
1381 public void setProxyHost(String host) throws IllegalStateException {
1382 if (hasConnection()) {
1383 wrappedConnection.setProxyHost(host);
1384 } else {
1385
1386 }
1387 }
1388
1389 public void setProxyPort(int port) throws IllegalStateException {
1390 if (hasConnection()) {
1391 wrappedConnection.setProxyPort(port);
1392 } else {
1393
1394 }
1395 }
1396
1397 /***
1398 * @deprecated
1399 */
1400 public void setSoTimeout(int timeout)
1401 throws SocketException, IllegalStateException {
1402 if (hasConnection()) {
1403 wrappedConnection.setSoTimeout(timeout);
1404 } else {
1405
1406 }
1407 }
1408
1409 /***
1410 * @deprecated
1411 */
1412 public void shutdownOutput() {
1413 if (hasConnection()) {
1414 wrappedConnection.shutdownOutput();
1415 } else {
1416
1417 }
1418 }
1419
1420 public void tunnelCreated() throws IllegalStateException, IOException {
1421 if (hasConnection()) {
1422 wrappedConnection.tunnelCreated();
1423 } else {
1424
1425 }
1426 }
1427
1428 public void write(byte[] data, int offset, int length)
1429 throws IOException, IllegalStateException, HttpRecoverableException {
1430 if (hasConnection()) {
1431 wrappedConnection.write(data, offset, length);
1432 } else {
1433 throw new IllegalStateException("Connection has been released");
1434 }
1435 }
1436
1437 public void write(byte[] data)
1438 throws IOException, IllegalStateException, HttpRecoverableException {
1439 if (hasConnection()) {
1440 wrappedConnection.write(data);
1441 } else {
1442 throw new IllegalStateException("Connection has been released");
1443 }
1444 }
1445
1446 public void writeLine()
1447 throws IOException, IllegalStateException, HttpRecoverableException {
1448 if (hasConnection()) {
1449 wrappedConnection.writeLine();
1450 } else {
1451 throw new IllegalStateException("Connection has been released");
1452 }
1453 }
1454
1455 public void writeLine(byte[] data)
1456 throws IOException, IllegalStateException, HttpRecoverableException {
1457 if (hasConnection()) {
1458 wrappedConnection.writeLine(data);
1459 } else {
1460 throw new IllegalStateException("Connection has been released");
1461 }
1462 }
1463
1464 public void flushRequestOutputStream() throws IOException {
1465 if (hasConnection()) {
1466 wrappedConnection.flushRequestOutputStream();
1467 } else {
1468 throw new IllegalStateException("Connection has been released");
1469 }
1470 }
1471
1472 /***
1473 * @deprecated
1474 */
1475 public int getSoTimeout() throws SocketException {
1476 if (hasConnection()) {
1477 return wrappedConnection.getSoTimeout();
1478 } else {
1479 throw new IllegalStateException("Connection has been released");
1480 }
1481 }
1482
1483 public String getVirtualHost() {
1484 if (hasConnection()) {
1485 return wrappedConnection.getVirtualHost();
1486 } else {
1487 throw new IllegalStateException("Connection has been released");
1488 }
1489 }
1490
1491 public void setVirtualHost(String host) throws IllegalStateException {
1492 if (hasConnection()) {
1493 wrappedConnection.setVirtualHost(host);
1494 } else {
1495 throw new IllegalStateException("Connection has been released");
1496 }
1497 }
1498
1499 public int getSendBufferSize() throws SocketException {
1500 if (hasConnection()) {
1501 return wrappedConnection.getSendBufferSize();
1502 } else {
1503 throw new IllegalStateException("Connection has been released");
1504 }
1505 }
1506
1507 /***
1508 * @deprecated
1509 */
1510 public void setSendBufferSize(int sendBufferSize) throws SocketException {
1511 if (hasConnection()) {
1512 wrappedConnection.setSendBufferSize(sendBufferSize);
1513 } else {
1514 throw new IllegalStateException("Connection has been released");
1515 }
1516 }
1517
1518 public HttpConnectionParams getParams() {
1519 if (hasConnection()) {
1520 return wrappedConnection.getParams();
1521 } else {
1522 throw new IllegalStateException("Connection has been released");
1523 }
1524 }
1525
1526 public void setParams(final HttpConnectionParams params) {
1527 if (hasConnection()) {
1528 wrappedConnection.setParams(params);
1529 } else {
1530 throw new IllegalStateException("Connection has been released");
1531 }
1532 }
1533
1534
1535
1536
1537 public void print(String data, String charset) throws IOException, IllegalStateException {
1538 if (hasConnection()) {
1539 wrappedConnection.print(data, charset);
1540 } else {
1541 throw new IllegalStateException("Connection has been released");
1542 }
1543 }
1544
1545
1546
1547
1548 public void printLine(String data, String charset)
1549 throws IOException, IllegalStateException {
1550 if (hasConnection()) {
1551 wrappedConnection.printLine(data, charset);
1552 } else {
1553 throw new IllegalStateException("Connection has been released");
1554 }
1555 }
1556
1557
1558
1559
1560 public void setSocketTimeout(int timeout) throws SocketException, IllegalStateException {
1561 if (hasConnection()) {
1562 wrappedConnection.setSocketTimeout(timeout);
1563 } else {
1564 throw new IllegalStateException("Connection has been released");
1565 }
1566 }
1567
1568 }
1569
1570 }
1571