1 /*
2 * $Header: /home/cvs/jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/MultiThreadedHttpConnectionManager.java,v 1.17.2.6 2004/01/17 05:43:14 mbecke Exp $
3 * $Revision: 1.17.2.6 $
4 * $Date: 2004/01/17 05:43:14 $
5 *
6 * ====================================================================
7 *
8 * The Apache Software License, Version 1.1
9 *
10 * Copyright (c) 2002-2003 The Apache Software Foundation. All rights
11 * reserved.
12 *
13 * Redistribution and use in source and binary forms, with or without
14 * modification, are permitted provided that the following conditions
15 * are met:
16 *
17 * 1. Redistributions of source code must retain the above copyright
18 * notice, this list of conditions and the following disclaimer.
19 *
20 * 2. Redistributions in binary form must reproduce the above copyright
21 * notice, this list of conditions and the following disclaimer in
22 * the documentation and/or other materials provided with the
23 * distribution.
24 *
25 * 3. The end-user documentation included with the redistribution, if
26 * any, must include the following acknowlegement:
27 * "This product includes software developed by the
28 * Apache Software Foundation (http://www.apache.org/)."
29 * Alternately, this acknowlegement may appear in the software itself,
30 * if and wherever such third-party acknowlegements normally appear.
31 *
32 * 4. The names "The Jakarta Project", "Commons", and "Apache Software
33 * Foundation" must not be used to endorse or promote products derived
34 * from this software without prior written permission. For written
35 * permission, please contact apache@apache.org.
36 *
37 * 5. Products derived from this software may not be called "Apache"
38 * nor may "Apache" appear in their names without prior written
39 * permission of the Apache Group.
40 *
41 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
42 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
43 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
44 * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
45 * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
46 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
47 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
48 * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
49 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
50 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
51 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
52 * SUCH DAMAGE.
53 * ====================================================================
54 *
55 * This software consists of voluntary contributions made by many
56 * individuals on behalf of the Apache Software Foundation. For more
57 * information on the Apache Software Foundation, please see
58 * <http://www.apache.org/>.
59 *
60 * [Additional notices, if required by prior licensing conditions]
61 *
62 */
63
64 package org.apache.commons.httpclient;
65
66 import java.io.IOException;
67 import java.io.InputStream;
68 import java.io.OutputStream;
69 import java.lang.ref.Reference;
70 import java.lang.ref.ReferenceQueue;
71 import java.lang.ref.WeakReference;
72 import java.net.InetAddress;
73 import java.net.SocketException;
74 import java.util.Collections;
75 import java.util.HashMap;
76 import java.util.Iterator;
77 import java.util.LinkedList;
78 import java.util.Map;
79
80 import org.apache.commons.httpclient.protocol.Protocol;
81 import org.apache.commons.logging.Log;
82 import org.apache.commons.logging.LogFactory;
83
84 /***
85 * Manages a set of HttpConnections for various HostConfigurations.
86 *
87 * @author <a href="mailto:becke@u.washington.edu">Michael Becke</a>
88 * @author Eric Johnson
89 * @author <a href="mailto:mbowler@GargoyleSoftware.com">Mike Bowler</a>
90 * @author Carl A. Dunham
91 *
92 * @since 2.0
93 */
94 public class MultiThreadedHttpConnectionManager implements HttpConnectionManager {
95
96 // -------------------------------------------------------- Class Variables
97 /*** Log object for this class. */
98 private static final Log LOG = LogFactory.getLog(MultiThreadedHttpConnectionManager.class);
99
100 /*** The default maximum number of connections allowed per host */
101 public static final int DEFAULT_MAX_HOST_CONNECTIONS = 2; // Per RFC 2616 sec 8.1.4
102
103 /*** The default maximum number of connections allowed overall */
104 public static final int DEFAULT_MAX_TOTAL_CONNECTIONS = 20;
105
106 /***
107 * A mapping from Reference to ConnectionSource. Used to reclaim resources when connections
108 * are lost to the garbage collector.
109 */
110 public static final Map REFERENCE_TO_CONNECTION_SOURCE = Collections.synchronizedMap(new HashMap());
111
112 /***
113 * The reference queue used to track when HttpConnections are lost to the
114 * garbage collector
115 */
116 private static final ReferenceQueue REFERENCE_QUEUE = new ReferenceQueue();
117
118 /***
119 * The thread responsible for handling lost connections.
120 */
121 private static ReferenceQueueThread REFERENCE_QUEUE_THREAD;
122
123 static {
124 REFERENCE_QUEUE_THREAD = new ReferenceQueueThread();
125 REFERENCE_QUEUE_THREAD.start();
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 REFERENCE_TO_CONNECTION_SOURCE.put(
156 connection.reference,
157 source
158 );
159 }
160
161 /***
162 * Removes the reference being stored for the given connection. This method should be called
163 * when the connection manager again has a direct reference to the connection.
164 *
165 * @param connection the connection to remove the reference for
166 *
167 * @see #storeReferenceToConnection(HttpConnection, HostConfiguration, ConnectionPool)
168 */
169 private static void removeReferenceToConnection(HttpConnectionWithReference connection) {
170
171 synchronized (REFERENCE_TO_CONNECTION_SOURCE) {
172 REFERENCE_TO_CONNECTION_SOURCE.remove(connection.reference);
173 }
174 }
175
176 // ----------------------------------------------------- Instance Variables
177 /*** Maximum number of connections allowed per host */
178 private int maxHostConnections = DEFAULT_MAX_HOST_CONNECTIONS;
179
180 /*** Maximum number of connections allowed overall */
181 private int maxTotalConnections = DEFAULT_MAX_TOTAL_CONNECTIONS;
182
183 /*** The value to set when calling setStaleCheckingEnabled() on each connection */
184 private boolean connectionStaleCheckingEnabled = true;
185
186 /*** Connection Pool */
187 private ConnectionPool connectionPool;
188
189 /***
190 * No-args constructor
191 */
192 public MultiThreadedHttpConnectionManager() {
193 this.connectionPool = new ConnectionPool();
194 }
195
196 /***
197 * Gets the staleCheckingEnabled value to be set on HttpConnections that are created.
198 *
199 * @return <code>true</code> if stale checking will be enabled on HttpConections
200 *
201 * @see HttpConnection#isStaleCheckingEnabled()
202 */
203 public boolean isConnectionStaleCheckingEnabled() {
204 return connectionStaleCheckingEnabled;
205 }
206
207 /***
208 * Sets the staleCheckingEnabled value to be set on HttpConnections that are created.
209 *
210 * @param connectionStaleCheckingEnabled <code>true</code> if stale checking will be enabled
211 * on HttpConections
212 *
213 * @see HttpConnection#setStaleCheckingEnabled(boolean)
214 */
215 public void setConnectionStaleCheckingEnabled(boolean connectionStaleCheckingEnabled) {
216 this.connectionStaleCheckingEnabled = connectionStaleCheckingEnabled;
217 }
218
219 /***
220 * Sets the maximum number of connections allowed for a given
221 * HostConfiguration. Per RFC 2616 section 8.1.4, this value defaults to 2.
222 *
223 * @param maxHostConnections the number of connections allowed for each
224 * hostConfiguration
225 */
226 public void setMaxConnectionsPerHost(int maxHostConnections) {
227 this.maxHostConnections = maxHostConnections;
228 }
229
230 /***
231 * Gets the maximum number of connections allowed for a given
232 * hostConfiguration.
233 *
234 * @return The maximum number of connections allowed for a given
235 * hostConfiguration.
236 */
237 public int getMaxConnectionsPerHost() {
238 return maxHostConnections;
239 }
240
241 /***
242 * Sets the maximum number of connections allowed in the system.
243 *
244 * @param maxTotalConnections the maximum number of connections allowed
245 */
246 public void setMaxTotalConnections(int maxTotalConnections) {
247 this.maxTotalConnections = maxTotalConnections;
248 }
249
250 /***
251 * Gets the maximum number of connections allowed in the system.
252 *
253 * @return The maximum number of connections allowed
254 */
255 public int getMaxTotalConnections() {
256 return maxTotalConnections;
257 }
258
259 /***
260 * @see HttpConnectionManager#getConnection(HostConfiguration)
261 */
262 public HttpConnection getConnection(HostConfiguration hostConfiguration) {
263
264 while (true) {
265 try {
266 return getConnection(hostConfiguration, 0);
267 } catch (HttpException e) {
268 // we'll go ahead and log this, but it should never happen. HttpExceptions
269 // are only thrown when the timeout occurs and since we have no timeout
270 // it should never happen.
271 LOG.debug(
272 "Unexpected exception while waiting for connection",
273 e
274 );
275 };
276 }
277 }
278
279 /***
280 * @see HttpConnectionManager#getConnection(HostConfiguration, long)
281 */
282 public HttpConnection getConnection(HostConfiguration hostConfiguration,
283 long timeout) throws HttpException {
284
285 LOG.trace("enter HttpConnectionManager.getConnection(HostConfiguration, long)");
286
287 if (hostConfiguration == null) {
288 throw new IllegalArgumentException("hostConfiguration is null");
289 }
290
291 if (LOG.isDebugEnabled()) {
292 LOG.debug("HttpConnectionManager.getConnection: config = "
293 + hostConfiguration + ", timeout = " + timeout);
294 }
295
296 final HttpConnection conn = doGetConnection(hostConfiguration, timeout);
297
298 // wrap the connection in an adapter so we can ensure it is used
299 // only once
300 return new HttpConnectionAdapter(conn);
301 }
302
303 /***
304 * Gets a connection or waits if one is not available. A connection is
305 * available if one exists that is not being used or if fewer than
306 * maxHostConnections have been created in the connectionPool, and fewer
307 * than maxTotalConnections have been created in all connectionPools.
308 *
309 * @param hostConfiguration The host configuration.
310 * @param timeout the number of milliseconds to wait for a connection, 0 to
311 * wait indefinitely
312 *
313 * @return HttpConnection an available connection
314 *
315 * @throws HttpException if a connection does not become available in
316 * 'timeout' milliseconds
317 */
318 private HttpConnection doGetConnection(HostConfiguration hostConfiguration,
319 long timeout) throws HttpException {
320
321 HttpConnection connection = null;
322
323 synchronized (connectionPool) {
324
325 // we clone the hostConfiguration
326 // so that it cannot be changed once the connection has been retrieved
327 hostConfiguration = new HostConfiguration(hostConfiguration);
328 HostConnectionPool hostPool = connectionPool.getHostPool(hostConfiguration);
329 WaitingThread waitingThread = null;
330
331 boolean useTimeout = (timeout > 0);
332 long timeToWait = timeout;
333 long startWait = 0;
334 long endWait = 0;
335
336 while (connection == null) {
337
338 // happen to have a free connection with the right specs
339 //
340 if (hostPool.freeConnections.size() > 0) {
341 connection = connectionPool.getFreeConnection(hostConfiguration);
342
343 // have room to make more
344 //
345 } else if ((hostPool.numConnections < maxHostConnections)
346 && (connectionPool.numConnections < maxTotalConnections)) {
347
348 connection = connectionPool.createConnection(hostConfiguration);
349
350 // have room to add host connection, and there is at least one free
351 // connection that can be liberated to make overall room
352 //
353 } else if ((hostPool.numConnections < maxHostConnections)
354 && (connectionPool.freeConnections.size() > 0)) {
355
356 connectionPool.deleteLeastUsedConnection();
357 connection = connectionPool.createConnection(hostConfiguration);
358
359 // otherwise, we have to wait for one of the above conditions to
360 // become true
361 //
362 } else {
363 // TODO: keep track of which hostConfigurations have waiting
364 // threads, so they avoid being sacrificed before necessary
365
366 try {
367
368 if (useTimeout && timeToWait <= 0) {
369 throw new HttpException("Timeout waiting for connection");
370 }
371
372 if (LOG.isDebugEnabled()) {
373 LOG.debug("Unable to get a connection, waiting..., hostConfig=" + hostConfiguration);
374 }
375
376 if (waitingThread == null) {
377 waitingThread = new WaitingThread();
378 waitingThread.hostConnectionPool = hostPool;
379 waitingThread.thread = Thread.currentThread();
380 }
381
382 if (useTimeout) {
383 startWait = System.currentTimeMillis();
384 }
385
386 hostPool.waitingThreads.addLast(waitingThread);
387 connectionPool.waitingThreads.addLast(waitingThread);
388 connectionPool.wait(timeToWait);
389
390 // we have not been interrupted so we need to remove ourselves from the
391 // wait queue
392 hostPool.waitingThreads.remove(waitingThread);
393 connectionPool.waitingThreads.remove(waitingThread);
394 } catch (InterruptedException e) {
395 // do nothing
396 } finally {
397 if (useTimeout) {
398 endWait = System.currentTimeMillis();
399 timeToWait -= (endWait - startWait);
400 }
401 }
402 }
403 }
404 }
405 return connection;
406 }
407
408 /***
409 * Gets the number of connections in use for this configuration.
410 *
411 * @param hostConfiguration the key that connections are tracked on
412 * @return the number of connections in use
413 */
414 public int getConnectionsInUse(HostConfiguration hostConfiguration) {
415 synchronized (connectionPool) {
416 HostConnectionPool hostPool = connectionPool.getHostPool(hostConfiguration);
417 return hostPool.numConnections;
418 }
419 }
420
421 /***
422 * Gets the total number of connections in use.
423 *
424 * @return the total number of connections in use
425 */
426 public int getConnectionsInUse() {
427 synchronized (connectionPool) {
428 return connectionPool.numConnections;
429 }
430 }
431
432 /***
433 * Make the given HttpConnection available for use by other requests.
434 * If another thread is blocked in getConnection() that could use this
435 * connection, it will be woken up.
436 *
437 * @param conn the HttpConnection to make available.
438 */
439 public void releaseConnection(HttpConnection conn) {
440 LOG.trace("enter HttpConnectionManager.releaseConnection(HttpConnection)");
441
442 if (conn instanceof HttpConnectionAdapter) {
443 // connections given out are wrapped in an HttpConnectionAdapter
444 conn = ((HttpConnectionAdapter) conn).getWrappedConnection();
445 } else {
446 // this is okay, when an HttpConnectionAdapter is released
447 // is releases the real connection
448 }
449
450 // make sure that the response has been read.
451 SimpleHttpConnectionManager.finishLastResponse(conn);
452
453 connectionPool.freeConnection(conn);
454 }
455
456 /***
457 * Gets the host configuration for a connection.
458 * @param conn the connection to get the configuration of
459 * @return a new HostConfiguration
460 */
461 private HostConfiguration configurationForConnection(HttpConnection conn) {
462
463 HostConfiguration connectionConfiguration = new HostConfiguration();
464
465 connectionConfiguration.setHost(
466 conn.getHost(),
467 conn.getVirtualHost(),
468 conn.getPort(),
469 conn.getProtocol()
470 );
471 if (conn.getLocalAddress() != null) {
472 connectionConfiguration.setLocalAddress(conn.getLocalAddress());
473 }
474 if (conn.getProxyHost() != null) {
475 connectionConfiguration.setProxy(conn.getProxyHost(), conn.getProxyPort());
476 }
477
478 return connectionConfiguration;
479 }
480
481
482 /***
483 * Global Connection Pool, including per-host pools
484 */
485 private class ConnectionPool {
486
487 /*** The list of free connections */
488 private LinkedList freeConnections = new LinkedList();
489
490 /*** The list of WaitingThreads waiting for a connection */
491 private LinkedList waitingThreads = new LinkedList();
492
493 /***
494 * Map where keys are {@link HostConfiguration}s and values are {@link
495 * HostConnectionPool}s
496 */
497 private final Map mapHosts = new HashMap();
498
499 /*** The number of created connections */
500 private int numConnections = 0;
501
502 /***
503 * Creates a new connection and returns is for use of the calling method.
504 *
505 * @param hostConfiguration the configuration for the connection
506 * @return a new connection or <code>null</code> if none are available
507 */
508 public synchronized HttpConnection createConnection(HostConfiguration hostConfiguration) {
509
510 HttpConnectionWithReference connection = null;
511
512 HostConnectionPool hostPool = getHostPool(hostConfiguration);
513
514 if ((hostPool.numConnections < getMaxConnectionsPerHost())
515 && (numConnections < getMaxTotalConnections())) {
516
517 if (LOG.isDebugEnabled()) {
518 LOG.debug("Allocating new connection, hostConfig=" + hostConfiguration);
519 }
520 connection = new HttpConnectionWithReference(hostConfiguration);
521 connection.setStaleCheckingEnabled(connectionStaleCheckingEnabled);
522 connection.setHttpConnectionManager(MultiThreadedHttpConnectionManager.this);
523 numConnections++;
524 hostPool.numConnections++;
525
526 // store a reference to this connection so that it can be cleaned up
527 // in the event it is not correctly released
528 storeReferenceToConnection(connection, hostConfiguration, this);
529
530 } else if (LOG.isDebugEnabled()) {
531 if (hostPool.numConnections >= getMaxConnectionsPerHost()) {
532 LOG.debug("No connection allocated, host pool has already reached "
533 + "maxConnectionsPerHost, hostConfig=" + hostConfiguration
534 + ", maxConnectionsPerhost=" + getMaxConnectionsPerHost());
535 } else {
536 LOG.debug("No connection allocated, maxTotalConnections reached, "
537 + "maxTotalConnections=" + getMaxTotalConnections());
538 }
539 }
540
541 return connection;
542 }
543
544 /***
545 * Handles cleaning up for a lost connection with the given config. Decrements any
546 * connection counts and notifies waiting threads, if appropriate.
547 *
548 * @param config the host configuration of the connection that was lost
549 */
550 public synchronized void handleLostConnection(HostConfiguration config) {
551 HostConnectionPool hostPool = getHostPool(config);
552 hostPool.numConnections--;
553
554 numConnections--;
555 notifyWaitingThread(config);
556 }
557
558 /***
559 * Get the pool (list) of connections available for the given hostConfig.
560 *
561 * @param hostConfiguration the configuraton for the connection pool
562 * @return a pool (list) of connections available for the given config
563 */
564 public synchronized HostConnectionPool getHostPool(HostConfiguration hostConfiguration) {
565 LOG.trace("enter HttpConnectionManager.ConnectionPool.getHostPool(HostConfiguration)");
566
567 // Look for a list of connections for the given config
568 HostConnectionPool listConnections = (HostConnectionPool)
569 mapHosts.get(hostConfiguration);
570 if (listConnections == null) {
571 // First time for this config
572 listConnections = new HostConnectionPool();
573 listConnections.hostConfiguration = hostConfiguration;
574 mapHosts.put(hostConfiguration, listConnections);
575 }
576
577 return listConnections;
578 }
579
580 /***
581 * If available, get a free connection for this host
582 *
583 * @param hostConfiguration the configuraton for the connection pool
584 * @return an available connection for the given config
585 */
586 public synchronized HttpConnection getFreeConnection(HostConfiguration hostConfiguration) {
587
588 HttpConnectionWithReference connection = null;
589
590 HostConnectionPool hostPool = getHostPool(hostConfiguration);
591
592 if (hostPool.freeConnections.size() > 0) {
593 connection = (HttpConnectionWithReference) hostPool.freeConnections.removeFirst();
594 freeConnections.remove(connection);
595 // store a reference to this connection so that it can be cleaned up
596 // in the event it is not correctly released
597 storeReferenceToConnection(connection, hostConfiguration, this);
598 if (LOG.isDebugEnabled()) {
599 LOG.debug("Getting free connection, hostConfig=" + hostConfiguration);
600 }
601 } else if (LOG.isDebugEnabled()) {
602 LOG.debug("There were no free connections to get, hostConfig="
603 + hostConfiguration);
604 }
605 return connection;
606 }
607
608 /***
609 * Close and delete an old, unused connection to make room for a new one.
610 */
611 public synchronized void deleteLeastUsedConnection() {
612
613 HttpConnection connection = (HttpConnection) freeConnections.removeFirst();
614
615 if (connection != null) {
616 HostConfiguration connectionConfiguration = configurationForConnection(connection);
617
618 if (LOG.isDebugEnabled()) {
619 LOG.debug("Reclaiming unused connection, hostConfig="
620 + connectionConfiguration);
621 }
622
623 connection.close();
624
625 HostConnectionPool hostPool = getHostPool(connectionConfiguration);
626
627 hostPool.freeConnections.remove(connection);
628 hostPool.numConnections--;
629 numConnections--;
630 } else if (LOG.isDebugEnabled()) {
631 LOG.debug("Attempted to reclaim an unused connection but there were none.");
632 }
633 }
634
635 /***
636 * Notifies a waiting thread that a connection for the given configuration is
637 * available.
638 * @param configuration the host config to use for notifying
639 * @see #notifyWaitingThread(HostConnectionPool)
640 */
641 public synchronized void notifyWaitingThread(HostConfiguration configuration) {
642 notifyWaitingThread(getHostPool(configuration));
643 }
644
645 /***
646 * Notifies a waiting thread that a connection for the given configuration is
647 * available. This will wake a thread witing in tis hostPool or if there is not
648 * one a thread in the ConnectionPool will be notified.
649 *
650 * @param hostPool the host pool to use for notifying
651 */
652 public synchronized void notifyWaitingThread(HostConnectionPool hostPool) {
653
654 // find the thread we are going to notify, we want to ensure that each
655 // waiting thread is only interrupted once so we will remove it from
656 // all wait queues before interrupting it
657 WaitingThread waitingThread = null;
658
659 if (hostPool.waitingThreads.size() > 0) {
660 if (LOG.isDebugEnabled()) {
661 LOG.debug("Notifying thread waiting on host pool, hostConfig="
662 + hostPool.hostConfiguration);
663 }
664 waitingThread = (WaitingThread) hostPool.waitingThreads.removeFirst();
665 waitingThreads.remove(waitingThread);
666 } else if (waitingThreads.size() > 0) {
667 if (LOG.isDebugEnabled()) {
668 LOG.debug("No-one waiting on host pool, notifying next waiting thread.");
669 }
670 waitingThread = (WaitingThread) waitingThreads.removeFirst();
671 waitingThread.hostConnectionPool.waitingThreads.remove(waitingThread);
672 } else if (LOG.isDebugEnabled()) {
673 LOG.debug("Notifying no-one, there are no waiting threads");
674 }
675
676 if (waitingThread != null) {
677 waitingThread.thread.interrupt();
678 }
679 }
680
681 /***
682 * Marks the given connection as free.
683 * @param conn a connection that is no longer being used
684 */
685 public void freeConnection(HttpConnection conn) {
686
687 HostConfiguration connectionConfiguration = configurationForConnection(conn);
688
689 if (LOG.isDebugEnabled()) {
690 LOG.debug("Freeing connection, hostConfig=" + connectionConfiguration);
691 }
692
693 synchronized (this) {
694 HostConnectionPool hostPool = getHostPool(connectionConfiguration);
695
696 // Put the connect back in the available list and notify a waiter
697 hostPool.freeConnections.add(conn);
698 if (hostPool.numConnections == 0) {
699 // for some reason this connection pool didn't already exist
700 LOG.error("Host connection pool not found, hostConfig="
701 + connectionConfiguration);
702 hostPool.numConnections = 1;
703 }
704
705 freeConnections.add(conn);
706 // we can remove the reference to this connection as we have control over
707 // it again. this also ensures that the connection manager can be GCed
708 removeReferenceToConnection((HttpConnectionWithReference) conn);
709 if (numConnections == 0) {
710 // for some reason this connection pool didn't already exist
711 LOG.error("Host connection pool not found, hostConfig="
712 + connectionConfiguration);
713 numConnections = 1;
714 }
715
716 notifyWaitingThread(hostPool);
717 }
718 }
719 }
720
721 /***
722 * A simple struct-like class to combine the objects needed to release a connection's
723 * resources when claimed by the garbage collector.
724 */
725 private static class ConnectionSource {
726
727 /*** The connection pool that created the connection */
728 public ConnectionPool connectionPool;
729
730 /*** The connection's host configuration */
731 public HostConfiguration hostConfiguration;
732 }
733
734 /***
735 * A simple struct-like class to combine the connection list and the count
736 * of created connections.
737 */
738 private static class HostConnectionPool {
739 /*** The hostConfig this pool is for */
740 public HostConfiguration hostConfiguration;
741
742 /*** The list of free connections */
743 public LinkedList freeConnections = new LinkedList();
744
745 /*** The list of WaitingThreads for this host */
746 public LinkedList waitingThreads = new LinkedList();
747
748 /*** The number of created connections */
749 public int numConnections = 0;
750 }
751
752 /***
753 * A simple struct-like class to combine the waiting thread and the connection
754 * pool it is waiting on.
755 */
756 private static class WaitingThread {
757 /*** The thread that is waiting for a connection */
758 public Thread thread;
759
760 /*** The connection pool the thread is waiting for */
761 public HostConnectionPool hostConnectionPool;
762 }
763
764 /***
765 * A thread for listening for HttpConnections reclaimed by the garbage
766 * collector.
767 */
768 private static class ReferenceQueueThread extends Thread {
769
770 /***
771 * Create an instance and make this a daemon thread.
772 */
773 public ReferenceQueueThread() {
774 setDaemon(true);
775 setName("MultiThreadedHttpConnectionManager cleanup");
776 }
777
778 /***
779 * Handles cleaning up for the given connection reference.
780 *
781 * @param ref the reference to clean up
782 */
783 private void handleReference(Reference ref) {
784
785 ConnectionSource source = (ConnectionSource) REFERENCE_TO_CONNECTION_SOURCE.remove(ref);
786 // only clean up for this reference if it is still associated with
787 // a ConnectionSource
788 if (source != null) {
789 if (LOG.isDebugEnabled()) {
790 LOG.debug(
791 "Connection reclaimed by garbage collector, hostConfig="
792 + source.hostConfiguration);
793 }
794
795 source.connectionPool.handleLostConnection(source.hostConfiguration);
796 }
797 }
798
799 /***
800 * Start execution.
801 */
802 public void run() {
803 while (true) {
804 try {
805 Reference ref = REFERENCE_QUEUE.remove();
806 if (ref != null) {
807 handleReference(ref);
808 }
809 } catch (InterruptedException e) {
810 LOG.debug("ReferenceQueueThread interrupted", e);
811 }
812 }
813 }
814
815 }
816
817 /***
818 * A connection that keeps a reference to itself.
819 */
820 private static class HttpConnectionWithReference extends HttpConnection {
821
822 public WeakReference reference = new WeakReference(this, REFERENCE_QUEUE);
823
824 /***
825 * @param hostConfiguration
826 */
827 public HttpConnectionWithReference(HostConfiguration hostConfiguration) {
828 super(hostConfiguration);
829 }
830
831 }
832
833 /***
834 * An HttpConnection wrapper that ensures a connection cannot be used
835 * once released.
836 */
837 private static class HttpConnectionAdapter extends HttpConnection {
838
839 // the wrapped connection
840 private HttpConnection wrappedConnection;
841
842 /***
843 * Creates a new HttpConnectionAdapter.
844 * @param connection the connection to be wrapped
845 */
846 public HttpConnectionAdapter(HttpConnection connection) {
847 super(connection.getHost(), connection.getPort(), connection.getProtocol());
848 this.wrappedConnection = connection;
849 }
850
851 /***
852 * Tests if the wrapped connection is still available.
853 * @return boolean
854 */
855 protected boolean hasConnection() {
856 return wrappedConnection != null;
857 }
858
859 /***
860 * @return HttpConnection
861 */
862 HttpConnection getWrappedConnection() {
863 return wrappedConnection;
864 }
865
866 public void close() {
867 if (hasConnection()) {
868 wrappedConnection.close();
869 } else {
870 // do nothing
871 }
872 }
873
874 public InetAddress getLocalAddress() {
875 if (hasConnection()) {
876 return wrappedConnection.getLocalAddress();
877 } else {
878 return null;
879 }
880 }
881
882 public boolean isStaleCheckingEnabled() {
883 if (hasConnection()) {
884 return wrappedConnection.isStaleCheckingEnabled();
885 } else {
886 return false;
887 }
888 }
889
890 public void setLocalAddress(InetAddress localAddress) {
891 if (hasConnection()) {
892 wrappedConnection.setLocalAddress(localAddress);
893 } else {
894 throw new IllegalStateException("Connection has been released");
895 }
896 }
897
898 public void setStaleCheckingEnabled(boolean staleCheckEnabled) {
899 if (hasConnection()) {
900 wrappedConnection.setStaleCheckingEnabled(staleCheckEnabled);
901 } else {
902 throw new IllegalStateException("Connection has been released");
903 }
904 }
905
906 public String getHost() {
907 if (hasConnection()) {
908 return wrappedConnection.getHost();
909 } else {
910 return null;
911 }
912 }
913
914 public HttpConnectionManager getHttpConnectionManager() {
915 if (hasConnection()) {
916 return wrappedConnection.getHttpConnectionManager();
917 } else {
918 return null;
919 }
920 }
921
922 public InputStream getLastResponseInputStream() {
923 if (hasConnection()) {
924 return wrappedConnection.getLastResponseInputStream();
925 } else {
926 return null;
927 }
928 }
929
930 public int getPort() {
931 if (hasConnection()) {
932 return wrappedConnection.getPort();
933 } else {
934 return -1;
935 }
936 }
937
938 public Protocol getProtocol() {
939 if (hasConnection()) {
940 return wrappedConnection.getProtocol();
941 } else {
942 return null;
943 }
944 }
945
946 public String getProxyHost() {
947 if (hasConnection()) {
948 return wrappedConnection.getProxyHost();
949 } else {
950 return null;
951 }
952 }
953
954 public int getProxyPort() {
955 if (hasConnection()) {
956 return wrappedConnection.getProxyPort();
957 } else {
958 return -1;
959 }
960 }
961
962 public OutputStream getRequestOutputStream()
963 throws IOException, IllegalStateException {
964 if (hasConnection()) {
965 return wrappedConnection.getRequestOutputStream();
966 } else {
967 return null;
968 }
969 }
970
971 public OutputStream getRequestOutputStream(boolean useChunking)
972 throws IOException, IllegalStateException {
973 if (hasConnection()) {
974 return wrappedConnection.getRequestOutputStream(useChunking);
975 } else {
976 return null;
977 }
978 }
979
980 public InputStream getResponseInputStream()
981 throws IOException, IllegalStateException {
982 if (hasConnection()) {
983 return wrappedConnection.getResponseInputStream();
984 } else {
985 return null;
986 }
987 }
988
989 public InputStream getResponseInputStream(HttpMethod method)
990 throws IOException, IllegalStateException {
991 if (hasConnection()) {
992 return wrappedConnection.getResponseInputStream(method);
993 } else {
994 return null;
995 }
996 }
997
998 public boolean isOpen() {
999 if (hasConnection()) {
1000 return wrappedConnection.isOpen();
1001 } else {
1002 return false;
1003 }
1004 }
1005
1006 public boolean isProxied() {
1007 if (hasConnection()) {
1008 return wrappedConnection.isProxied();
1009 } else {
1010 return false;
1011 }
1012 }
1013
1014 public boolean isResponseAvailable() throws IOException {
1015 if (hasConnection()) {
1016 return wrappedConnection.isResponseAvailable();
1017 } else {
1018 return false;
1019 }
1020 }
1021
1022 public boolean isResponseAvailable(int timeout) throws IOException {
1023 if (hasConnection()) {
1024 return wrappedConnection.isResponseAvailable(timeout);
1025 } else {
1026 return false;
1027 }
1028 }
1029
1030 public boolean isSecure() {
1031 if (hasConnection()) {
1032 return wrappedConnection.isSecure();
1033 } else {
1034 return false;
1035 }
1036 }
1037
1038 public boolean isTransparent() {
1039 if (hasConnection()) {
1040 return wrappedConnection.isTransparent();
1041 } else {
1042 return false;
1043 }
1044 }
1045
1046 public void open() throws IOException {
1047 if (hasConnection()) {
1048 wrappedConnection.open();
1049 } else {
1050 throw new IllegalStateException("Connection has been released");
1051 }
1052 }
1053
1054 public void print(String data)
1055 throws IOException, IllegalStateException, HttpRecoverableException {
1056 if (hasConnection()) {
1057 wrappedConnection.print(data);
1058 } else {
1059 throw new IllegalStateException("Connection has been released");
1060 }
1061 }
1062
1063 public void printLine()
1064 throws IOException, IllegalStateException, HttpRecoverableException {
1065 if (hasConnection()) {
1066 wrappedConnection.printLine();
1067 } else {
1068 throw new IllegalStateException("Connection has been released");
1069 }
1070 }
1071
1072 public void printLine(String data)
1073 throws IOException, IllegalStateException, HttpRecoverableException {
1074 if (hasConnection()) {
1075 wrappedConnection.printLine(data);
1076 } else {
1077 throw new IllegalStateException("Connection has been released");
1078 }
1079 }
1080
1081 public String readLine() throws IOException, IllegalStateException {
1082 if (hasConnection()) {
1083 return wrappedConnection.readLine();
1084 } else {
1085 throw new IllegalStateException("Connection has been released");
1086 }
1087 }
1088
1089 public void releaseConnection() {
1090 if (hasConnection()) {
1091 HttpConnection wrappedConnection = this.wrappedConnection;
1092 this.wrappedConnection = null;
1093 wrappedConnection.releaseConnection();
1094 } else {
1095 // do nothing
1096 }
1097 }
1098
1099 public void setConnectionTimeout(int timeout) {
1100 if (hasConnection()) {
1101 wrappedConnection.setConnectionTimeout(timeout);
1102 } else {
1103 // do nothing
1104 }
1105 }
1106
1107 public void setHost(String host) throws IllegalStateException {
1108 if (hasConnection()) {
1109 wrappedConnection.setHost(host);
1110 } else {
1111 // do nothing
1112 }
1113 }
1114
1115 public void setHttpConnectionManager(HttpConnectionManager httpConnectionManager) {
1116 if (hasConnection()) {
1117 wrappedConnection.setHttpConnectionManager(httpConnectionManager);
1118 } else {
1119 // do nothing
1120 }
1121 }
1122
1123 public void setLastResponseInputStream(InputStream inStream) {
1124 if (hasConnection()) {
1125 wrappedConnection.setLastResponseInputStream(inStream);
1126 } else {
1127 // do nothing
1128 }
1129 }
1130
1131 public void setPort(int port) throws IllegalStateException {
1132 if (hasConnection()) {
1133 wrappedConnection.setPort(port);
1134 } else {
1135 // do nothing
1136 }
1137 }
1138
1139 public void setProtocol(Protocol protocol) {
1140 if (hasConnection()) {
1141 wrappedConnection.setProtocol(protocol);
1142 } else {
1143 // do nothing
1144 }
1145 }
1146
1147 public void setProxyHost(String host) throws IllegalStateException {
1148 if (hasConnection()) {
1149 wrappedConnection.setProxyHost(host);
1150 } else {
1151 // do nothing
1152 }
1153 }
1154
1155 public void setProxyPort(int port) throws IllegalStateException {
1156 if (hasConnection()) {
1157 wrappedConnection.setProxyPort(port);
1158 } else {
1159 // do nothing
1160 }
1161 }
1162
1163 public void setSecure(boolean secure) throws IllegalStateException {
1164 if (hasConnection()) {
1165 wrappedConnection.setSecure(secure);
1166 } else {
1167 // do nothing
1168 }
1169 }
1170
1171 public void setSoTimeout(int timeout)
1172 throws SocketException, IllegalStateException {
1173 if (hasConnection()) {
1174 wrappedConnection.setSoTimeout(timeout);
1175 } else {
1176 // do nothing
1177 }
1178 }
1179
1180 public void shutdownOutput() {
1181 if (hasConnection()) {
1182 wrappedConnection.shutdownOutput();
1183 } else {
1184 // do nothing
1185 }
1186 }
1187
1188 public void tunnelCreated() throws IllegalStateException, IOException {
1189 if (hasConnection()) {
1190 wrappedConnection.tunnelCreated();
1191 } else {
1192 // do nothing
1193 }
1194 }
1195
1196 public void write(byte[] data, int offset, int length)
1197 throws IOException, IllegalStateException, HttpRecoverableException {
1198 if (hasConnection()) {
1199 wrappedConnection.write(data, offset, length);
1200 } else {
1201 throw new IllegalStateException("Connection has been released");
1202 }
1203 }
1204
1205 public void write(byte[] data)
1206 throws IOException, IllegalStateException, HttpRecoverableException {
1207 if (hasConnection()) {
1208 wrappedConnection.write(data);
1209 } else {
1210 throw new IllegalStateException("Connection has been released");
1211 }
1212 }
1213
1214 public void writeLine()
1215 throws IOException, IllegalStateException, HttpRecoverableException {
1216 if (hasConnection()) {
1217 wrappedConnection.writeLine();
1218 } else {
1219 throw new IllegalStateException("Connection has been released");
1220 }
1221 }
1222
1223 public void writeLine(byte[] data)
1224 throws IOException, IllegalStateException, HttpRecoverableException {
1225 if (hasConnection()) {
1226 wrappedConnection.writeLine(data);
1227 } else {
1228 throw new IllegalStateException("Connection has been released");
1229 }
1230 }
1231
1232 public void flushRequestOutputStream() throws IOException {
1233 if (hasConnection()) {
1234 wrappedConnection.flushRequestOutputStream();
1235 } else {
1236 throw new IllegalStateException("Connection has been released");
1237 }
1238 }
1239
1240 public int getSoTimeout() throws SocketException {
1241 if (hasConnection()) {
1242 return wrappedConnection.getSoTimeout();
1243 } else {
1244 throw new IllegalStateException("Connection has been released");
1245 }
1246 }
1247
1248 public String getVirtualHost() {
1249 if (hasConnection()) {
1250 return wrappedConnection.getVirtualHost();
1251 } else {
1252 throw new IllegalStateException("Connection has been released");
1253 }
1254 }
1255
1256 public void setVirtualHost(String host) throws IllegalStateException {
1257 if (hasConnection()) {
1258 wrappedConnection.setVirtualHost(host);
1259 } else {
1260 throw new IllegalStateException("Connection has been released");
1261 }
1262 }
1263
1264 public int getSendBufferSize() throws SocketException {
1265 if (hasConnection()) {
1266 return wrappedConnection.getSendBufferSize();
1267 } else {
1268 throw new IllegalStateException("Connection has been released");
1269 }
1270 }
1271
1272 public void setSendBufferSize(int sendBufferSize) throws SocketException {
1273 if (hasConnection()) {
1274 wrappedConnection.setSendBufferSize(sendBufferSize);
1275 } else {
1276 throw new IllegalStateException("Connection has been released");
1277 }
1278 }
1279
1280 }
1281
1282 }
1283
This page was automatically generated by Maven