View Javadoc
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