Thu, 22 Jan 2015 13:21:57 +0100
Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6
michael@0 | 1 | /* |
michael@0 | 2 | * ==================================================================== |
michael@0 | 3 | * |
michael@0 | 4 | * Licensed to the Apache Software Foundation (ASF) under one or more |
michael@0 | 5 | * contributor license agreements. See the NOTICE file distributed with |
michael@0 | 6 | * this work for additional information regarding copyright ownership. |
michael@0 | 7 | * The ASF licenses this file to You under the Apache License, Version 2.0 |
michael@0 | 8 | * (the "License"); you may not use this file except in compliance with |
michael@0 | 9 | * the License. You may obtain a copy of the License at |
michael@0 | 10 | * |
michael@0 | 11 | * http://www.apache.org/licenses/LICENSE-2.0 |
michael@0 | 12 | * |
michael@0 | 13 | * Unless required by applicable law or agreed to in writing, software |
michael@0 | 14 | * distributed under the License is distributed on an "AS IS" BASIS, |
michael@0 | 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
michael@0 | 16 | * See the License for the specific language governing permissions and |
michael@0 | 17 | * limitations under the License. |
michael@0 | 18 | * ==================================================================== |
michael@0 | 19 | * |
michael@0 | 20 | * This software consists of voluntary contributions made by many |
michael@0 | 21 | * individuals on behalf of the Apache Software Foundation. For more |
michael@0 | 22 | * information on the Apache Software Foundation, please see |
michael@0 | 23 | * <http://www.apache.org/>. |
michael@0 | 24 | * |
michael@0 | 25 | */ |
michael@0 | 26 | |
michael@0 | 27 | package ch.boye.httpclientandroidlib.impl.conn.tsccm; |
michael@0 | 28 | |
michael@0 | 29 | import java.io.IOException; |
michael@0 | 30 | import java.util.concurrent.TimeUnit; |
michael@0 | 31 | |
michael@0 | 32 | import ch.boye.httpclientandroidlib.androidextra.HttpClientAndroidLog; |
michael@0 | 33 | /* LogFactory removed by HttpClient for Android script. */ |
michael@0 | 34 | import ch.boye.httpclientandroidlib.annotation.ThreadSafe; |
michael@0 | 35 | import ch.boye.httpclientandroidlib.conn.params.ConnPerRouteBean; |
michael@0 | 36 | import ch.boye.httpclientandroidlib.conn.routing.HttpRoute; |
michael@0 | 37 | import ch.boye.httpclientandroidlib.conn.scheme.SchemeRegistry; |
michael@0 | 38 | import ch.boye.httpclientandroidlib.conn.ClientConnectionManager; |
michael@0 | 39 | import ch.boye.httpclientandroidlib.conn.ClientConnectionOperator; |
michael@0 | 40 | import ch.boye.httpclientandroidlib.conn.ClientConnectionRequest; |
michael@0 | 41 | import ch.boye.httpclientandroidlib.conn.ConnectionPoolTimeoutException; |
michael@0 | 42 | import ch.boye.httpclientandroidlib.conn.ManagedClientConnection; |
michael@0 | 43 | import ch.boye.httpclientandroidlib.conn.OperatedClientConnection; |
michael@0 | 44 | import ch.boye.httpclientandroidlib.params.HttpParams; |
michael@0 | 45 | import ch.boye.httpclientandroidlib.impl.conn.DefaultClientConnectionOperator; |
michael@0 | 46 | import ch.boye.httpclientandroidlib.impl.conn.SchemeRegistryFactory; |
michael@0 | 47 | |
michael@0 | 48 | /** |
michael@0 | 49 | * Manages a pool of {@link OperatedClientConnection client connections} and |
michael@0 | 50 | * is able to service connection requests from multiple execution threads. |
michael@0 | 51 | * Connections are pooled on a per route basis. A request for a route which |
michael@0 | 52 | * already the manager has persistent connections for available in the pool |
michael@0 | 53 | * will be services by leasing a connection from the pool rather than |
michael@0 | 54 | * creating a brand new connection. |
michael@0 | 55 | * <p> |
michael@0 | 56 | * ThreadSafeClientConnManager maintains a maximum limit of connection on |
michael@0 | 57 | * a per route basis and in total. Per default this implementation will |
michael@0 | 58 | * create no more than than 2 concurrent connections per given route |
michael@0 | 59 | * and no more 20 connections in total. For many real-world applications |
michael@0 | 60 | * these limits may prove too constraining, especially if they use HTTP |
michael@0 | 61 | * as a transport protocol for their services. Connection limits, however, |
michael@0 | 62 | * can be adjusted using HTTP parameters. |
michael@0 | 63 | * |
michael@0 | 64 | * @since 4.0 |
michael@0 | 65 | */ |
michael@0 | 66 | @ThreadSafe |
michael@0 | 67 | public class ThreadSafeClientConnManager implements ClientConnectionManager { |
michael@0 | 68 | |
michael@0 | 69 | public HttpClientAndroidLog log; |
michael@0 | 70 | |
michael@0 | 71 | /** The schemes supported by this connection manager. */ |
michael@0 | 72 | protected final SchemeRegistry schemeRegistry; // @ThreadSafe |
michael@0 | 73 | |
michael@0 | 74 | @Deprecated |
michael@0 | 75 | protected final AbstractConnPool connectionPool; |
michael@0 | 76 | |
michael@0 | 77 | /** The pool of connections being managed. */ |
michael@0 | 78 | protected final ConnPoolByRoute pool; |
michael@0 | 79 | |
michael@0 | 80 | /** The operator for opening and updating connections. */ |
michael@0 | 81 | protected final ClientConnectionOperator connOperator; // DefaultClientConnectionOperator is @ThreadSafe |
michael@0 | 82 | |
michael@0 | 83 | protected final ConnPerRouteBean connPerRoute; |
michael@0 | 84 | |
michael@0 | 85 | /** |
michael@0 | 86 | * Creates a new thread safe connection manager. |
michael@0 | 87 | * |
michael@0 | 88 | * @param schreg the scheme registry. |
michael@0 | 89 | */ |
michael@0 | 90 | public ThreadSafeClientConnManager(final SchemeRegistry schreg) { |
michael@0 | 91 | this(schreg, -1, TimeUnit.MILLISECONDS); |
michael@0 | 92 | } |
michael@0 | 93 | |
michael@0 | 94 | /** |
michael@0 | 95 | * @since 4.1 |
michael@0 | 96 | */ |
michael@0 | 97 | public ThreadSafeClientConnManager() { |
michael@0 | 98 | this(SchemeRegistryFactory.createDefault()); |
michael@0 | 99 | } |
michael@0 | 100 | |
michael@0 | 101 | /** |
michael@0 | 102 | * Creates a new thread safe connection manager. |
michael@0 | 103 | * |
michael@0 | 104 | * @param schreg the scheme registry. |
michael@0 | 105 | * @param connTTL max connection lifetime, <=0 implies "infinity" |
michael@0 | 106 | * @param connTTLTimeUnit TimeUnit of connTTL |
michael@0 | 107 | * |
michael@0 | 108 | * @since 4.1 |
michael@0 | 109 | */ |
michael@0 | 110 | public ThreadSafeClientConnManager(final SchemeRegistry schreg, |
michael@0 | 111 | long connTTL, TimeUnit connTTLTimeUnit) { |
michael@0 | 112 | super(); |
michael@0 | 113 | if (schreg == null) { |
michael@0 | 114 | throw new IllegalArgumentException("Scheme registry may not be null"); |
michael@0 | 115 | } |
michael@0 | 116 | this.log = new HttpClientAndroidLog(getClass()); |
michael@0 | 117 | this.schemeRegistry = schreg; |
michael@0 | 118 | this.connPerRoute = new ConnPerRouteBean(); |
michael@0 | 119 | this.connOperator = createConnectionOperator(schreg); |
michael@0 | 120 | this.pool = createConnectionPool(connTTL, connTTLTimeUnit) ; |
michael@0 | 121 | this.connectionPool = this.pool; |
michael@0 | 122 | } |
michael@0 | 123 | |
michael@0 | 124 | /** |
michael@0 | 125 | * Creates a new thread safe connection manager. |
michael@0 | 126 | * |
michael@0 | 127 | * @param params the parameters for this manager. |
michael@0 | 128 | * @param schreg the scheme registry. |
michael@0 | 129 | * |
michael@0 | 130 | * @deprecated use {@link ThreadSafeClientConnManager#ThreadSafeClientConnManager(SchemeRegistry)} |
michael@0 | 131 | */ |
michael@0 | 132 | @Deprecated |
michael@0 | 133 | public ThreadSafeClientConnManager(HttpParams params, |
michael@0 | 134 | SchemeRegistry schreg) { |
michael@0 | 135 | if (schreg == null) { |
michael@0 | 136 | throw new IllegalArgumentException("Scheme registry may not be null"); |
michael@0 | 137 | } |
michael@0 | 138 | this.log = new HttpClientAndroidLog(getClass()); |
michael@0 | 139 | this.schemeRegistry = schreg; |
michael@0 | 140 | this.connPerRoute = new ConnPerRouteBean(); |
michael@0 | 141 | this.connOperator = createConnectionOperator(schreg); |
michael@0 | 142 | this.pool = (ConnPoolByRoute) createConnectionPool(params) ; |
michael@0 | 143 | this.connectionPool = this.pool; |
michael@0 | 144 | } |
michael@0 | 145 | |
michael@0 | 146 | @Override |
michael@0 | 147 | protected void finalize() throws Throwable { |
michael@0 | 148 | try { |
michael@0 | 149 | shutdown(); |
michael@0 | 150 | } finally { |
michael@0 | 151 | super.finalize(); |
michael@0 | 152 | } |
michael@0 | 153 | } |
michael@0 | 154 | |
michael@0 | 155 | /** |
michael@0 | 156 | * Hook for creating the connection pool. |
michael@0 | 157 | * |
michael@0 | 158 | * @return the connection pool to use |
michael@0 | 159 | * |
michael@0 | 160 | * @deprecated use #createConnectionPool(long, TimeUnit)) |
michael@0 | 161 | */ |
michael@0 | 162 | @Deprecated |
michael@0 | 163 | protected AbstractConnPool createConnectionPool(final HttpParams params) { |
michael@0 | 164 | return new ConnPoolByRoute(connOperator, params); |
michael@0 | 165 | } |
michael@0 | 166 | |
michael@0 | 167 | /** |
michael@0 | 168 | * Hook for creating the connection pool. |
michael@0 | 169 | * |
michael@0 | 170 | * @return the connection pool to use |
michael@0 | 171 | * |
michael@0 | 172 | * @since 4.1 |
michael@0 | 173 | */ |
michael@0 | 174 | protected ConnPoolByRoute createConnectionPool(long connTTL, TimeUnit connTTLTimeUnit) { |
michael@0 | 175 | return new ConnPoolByRoute(connOperator, connPerRoute, 20, connTTL, connTTLTimeUnit); |
michael@0 | 176 | } |
michael@0 | 177 | |
michael@0 | 178 | /** |
michael@0 | 179 | * Hook for creating the connection operator. |
michael@0 | 180 | * It is called by the constructor. |
michael@0 | 181 | * Derived classes can override this method to change the |
michael@0 | 182 | * instantiation of the operator. |
michael@0 | 183 | * The default implementation here instantiates |
michael@0 | 184 | * {@link DefaultClientConnectionOperator DefaultClientConnectionOperator}. |
michael@0 | 185 | * |
michael@0 | 186 | * @param schreg the scheme registry. |
michael@0 | 187 | * |
michael@0 | 188 | * @return the connection operator to use |
michael@0 | 189 | */ |
michael@0 | 190 | protected ClientConnectionOperator |
michael@0 | 191 | createConnectionOperator(SchemeRegistry schreg) { |
michael@0 | 192 | |
michael@0 | 193 | return new DefaultClientConnectionOperator(schreg);// @ThreadSafe |
michael@0 | 194 | } |
michael@0 | 195 | |
michael@0 | 196 | public SchemeRegistry getSchemeRegistry() { |
michael@0 | 197 | return this.schemeRegistry; |
michael@0 | 198 | } |
michael@0 | 199 | |
michael@0 | 200 | public ClientConnectionRequest requestConnection( |
michael@0 | 201 | final HttpRoute route, |
michael@0 | 202 | final Object state) { |
michael@0 | 203 | |
michael@0 | 204 | final PoolEntryRequest poolRequest = pool.requestPoolEntry( |
michael@0 | 205 | route, state); |
michael@0 | 206 | |
michael@0 | 207 | return new ClientConnectionRequest() { |
michael@0 | 208 | |
michael@0 | 209 | public void abortRequest() { |
michael@0 | 210 | poolRequest.abortRequest(); |
michael@0 | 211 | } |
michael@0 | 212 | |
michael@0 | 213 | public ManagedClientConnection getConnection( |
michael@0 | 214 | long timeout, TimeUnit tunit) throws InterruptedException, |
michael@0 | 215 | ConnectionPoolTimeoutException { |
michael@0 | 216 | if (route == null) { |
michael@0 | 217 | throw new IllegalArgumentException("Route may not be null."); |
michael@0 | 218 | } |
michael@0 | 219 | |
michael@0 | 220 | if (log.isDebugEnabled()) { |
michael@0 | 221 | log.debug("Get connection: " + route + ", timeout = " + timeout); |
michael@0 | 222 | } |
michael@0 | 223 | |
michael@0 | 224 | BasicPoolEntry entry = poolRequest.getPoolEntry(timeout, tunit); |
michael@0 | 225 | return new BasicPooledConnAdapter(ThreadSafeClientConnManager.this, entry); |
michael@0 | 226 | } |
michael@0 | 227 | |
michael@0 | 228 | }; |
michael@0 | 229 | |
michael@0 | 230 | } |
michael@0 | 231 | |
michael@0 | 232 | public void releaseConnection(ManagedClientConnection conn, long validDuration, TimeUnit timeUnit) { |
michael@0 | 233 | |
michael@0 | 234 | if (!(conn instanceof BasicPooledConnAdapter)) { |
michael@0 | 235 | throw new IllegalArgumentException |
michael@0 | 236 | ("Connection class mismatch, " + |
michael@0 | 237 | "connection not obtained from this manager."); |
michael@0 | 238 | } |
michael@0 | 239 | BasicPooledConnAdapter hca = (BasicPooledConnAdapter) conn; |
michael@0 | 240 | if ((hca.getPoolEntry() != null) && (hca.getManager() != this)) { |
michael@0 | 241 | throw new IllegalArgumentException |
michael@0 | 242 | ("Connection not obtained from this manager."); |
michael@0 | 243 | } |
michael@0 | 244 | synchronized (hca) { |
michael@0 | 245 | BasicPoolEntry entry = (BasicPoolEntry) hca.getPoolEntry(); |
michael@0 | 246 | if (entry == null) { |
michael@0 | 247 | return; |
michael@0 | 248 | } |
michael@0 | 249 | try { |
michael@0 | 250 | // make sure that the response has been read completely |
michael@0 | 251 | if (hca.isOpen() && !hca.isMarkedReusable()) { |
michael@0 | 252 | // In MTHCM, there would be a call to |
michael@0 | 253 | // SimpleHttpConnectionManager.finishLastResponse(conn); |
michael@0 | 254 | // Consuming the response is handled outside in 4.0. |
michael@0 | 255 | |
michael@0 | 256 | // make sure this connection will not be re-used |
michael@0 | 257 | // Shut down rather than close, we might have gotten here |
michael@0 | 258 | // because of a shutdown trigger. |
michael@0 | 259 | // Shutdown of the adapter also clears the tracked route. |
michael@0 | 260 | hca.shutdown(); |
michael@0 | 261 | } |
michael@0 | 262 | } catch (IOException iox) { |
michael@0 | 263 | if (log.isDebugEnabled()) |
michael@0 | 264 | log.debug("Exception shutting down released connection.", |
michael@0 | 265 | iox); |
michael@0 | 266 | } finally { |
michael@0 | 267 | boolean reusable = hca.isMarkedReusable(); |
michael@0 | 268 | if (log.isDebugEnabled()) { |
michael@0 | 269 | if (reusable) { |
michael@0 | 270 | log.debug("Released connection is reusable."); |
michael@0 | 271 | } else { |
michael@0 | 272 | log.debug("Released connection is not reusable."); |
michael@0 | 273 | } |
michael@0 | 274 | } |
michael@0 | 275 | hca.detach(); |
michael@0 | 276 | pool.freeEntry(entry, reusable, validDuration, timeUnit); |
michael@0 | 277 | } |
michael@0 | 278 | } |
michael@0 | 279 | } |
michael@0 | 280 | |
michael@0 | 281 | public void shutdown() { |
michael@0 | 282 | log.debug("Shutting down"); |
michael@0 | 283 | pool.shutdown(); |
michael@0 | 284 | } |
michael@0 | 285 | |
michael@0 | 286 | /** |
michael@0 | 287 | * Gets the total number of pooled connections for the given route. |
michael@0 | 288 | * This is the total number of connections that have been created and |
michael@0 | 289 | * are still in use by this connection manager for the route. |
michael@0 | 290 | * This value will not exceed the maximum number of connections per host. |
michael@0 | 291 | * |
michael@0 | 292 | * @param route the route in question |
michael@0 | 293 | * |
michael@0 | 294 | * @return the total number of pooled connections for that route |
michael@0 | 295 | */ |
michael@0 | 296 | public int getConnectionsInPool(final HttpRoute route) { |
michael@0 | 297 | return pool.getConnectionsInPool(route); |
michael@0 | 298 | } |
michael@0 | 299 | |
michael@0 | 300 | /** |
michael@0 | 301 | * Gets the total number of pooled connections. This is the total number of |
michael@0 | 302 | * connections that have been created and are still in use by this connection |
michael@0 | 303 | * manager. This value will not exceed the maximum number of connections |
michael@0 | 304 | * in total. |
michael@0 | 305 | * |
michael@0 | 306 | * @return the total number of pooled connections |
michael@0 | 307 | */ |
michael@0 | 308 | public int getConnectionsInPool() { |
michael@0 | 309 | return pool.getConnectionsInPool(); |
michael@0 | 310 | } |
michael@0 | 311 | |
michael@0 | 312 | public void closeIdleConnections(long idleTimeout, TimeUnit tunit) { |
michael@0 | 313 | if (log.isDebugEnabled()) { |
michael@0 | 314 | log.debug("Closing connections idle longer than " + idleTimeout + " " + tunit); |
michael@0 | 315 | } |
michael@0 | 316 | pool.closeIdleConnections(idleTimeout, tunit); |
michael@0 | 317 | } |
michael@0 | 318 | |
michael@0 | 319 | public void closeExpiredConnections() { |
michael@0 | 320 | log.debug("Closing expired connections"); |
michael@0 | 321 | pool.closeExpiredConnections(); |
michael@0 | 322 | } |
michael@0 | 323 | |
michael@0 | 324 | /** |
michael@0 | 325 | * since 4.1 |
michael@0 | 326 | */ |
michael@0 | 327 | public int getMaxTotal() { |
michael@0 | 328 | return pool.getMaxTotalConnections(); |
michael@0 | 329 | } |
michael@0 | 330 | |
michael@0 | 331 | /** |
michael@0 | 332 | * since 4.1 |
michael@0 | 333 | */ |
michael@0 | 334 | public void setMaxTotal(int max) { |
michael@0 | 335 | pool.setMaxTotalConnections(max); |
michael@0 | 336 | } |
michael@0 | 337 | |
michael@0 | 338 | /** |
michael@0 | 339 | * @since 4.1 |
michael@0 | 340 | */ |
michael@0 | 341 | public int getDefaultMaxPerRoute() { |
michael@0 | 342 | return connPerRoute.getDefaultMaxPerRoute(); |
michael@0 | 343 | } |
michael@0 | 344 | |
michael@0 | 345 | /** |
michael@0 | 346 | * @since 4.1 |
michael@0 | 347 | */ |
michael@0 | 348 | public void setDefaultMaxPerRoute(int max) { |
michael@0 | 349 | connPerRoute.setDefaultMaxPerRoute(max); |
michael@0 | 350 | } |
michael@0 | 351 | |
michael@0 | 352 | /** |
michael@0 | 353 | * @since 4.1 |
michael@0 | 354 | */ |
michael@0 | 355 | public int getMaxForRoute(final HttpRoute route) { |
michael@0 | 356 | return connPerRoute.getMaxForRoute(route); |
michael@0 | 357 | } |
michael@0 | 358 | |
michael@0 | 359 | /** |
michael@0 | 360 | * @since 4.1 |
michael@0 | 361 | */ |
michael@0 | 362 | public void setMaxForRoute(final HttpRoute route, int max) { |
michael@0 | 363 | connPerRoute.setMaxForRoute(route, max); |
michael@0 | 364 | } |
michael@0 | 365 | |
michael@0 | 366 | } |
michael@0 | 367 |