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