SingleClientConnManager.java revision 9921905a96d7a4528cc30edc3a919f786821eb08
1/* 2 * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/impl/conn/SingleClientConnManager.java $ 3 * $Revision: 673450 $ 4 * $Date: 2008-07-02 10:35:05 -0700 (Wed, 02 Jul 2008) $ 5 * 6 * ==================================================================== 7 * Licensed to the Apache Software Foundation (ASF) under one 8 * or more contributor license agreements. See the NOTICE file 9 * distributed with this work for additional information 10 * regarding copyright ownership. The ASF licenses this file 11 * to you under the Apache License, Version 2.0 (the 12 * "License"); you may not use this file except in compliance 13 * with the License. You may obtain a copy of the License at 14 * 15 * http://www.apache.org/licenses/LICENSE-2.0 16 * 17 * Unless required by applicable law or agreed to in writing, 18 * software distributed under the License is distributed on an 19 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 20 * KIND, either express or implied. See the License for the 21 * specific language governing permissions and limitations 22 * under the License. 23 * ==================================================================== 24 * 25 * This software consists of voluntary contributions made by many 26 * individuals on behalf of the Apache Software Foundation. For more 27 * information on the Apache Software Foundation, please see 28 * <http://www.apache.org/>. 29 * 30 */ 31 32package org.apache.http.impl.conn; 33 34import dalvik.system.SocketTagger; 35import java.io.IOException; 36import java.net.Socket; 37import java.util.concurrent.TimeUnit; 38 39import org.apache.commons.logging.Log; 40import org.apache.commons.logging.LogFactory; 41import org.apache.http.conn.ClientConnectionManager; 42import org.apache.http.conn.ClientConnectionOperator; 43import org.apache.http.conn.ClientConnectionRequest; 44import org.apache.http.conn.ManagedClientConnection; 45import org.apache.http.conn.routing.HttpRoute; 46import org.apache.http.conn.routing.RouteTracker; 47import org.apache.http.conn.scheme.SchemeRegistry; 48import org.apache.http.params.HttpParams; 49 50 51/** 52 * A connection "manager" for a single connection. 53 * This manager is good only for single-threaded use. 54 * Allocation <i>always</i> returns the connection immediately, 55 * even if it has not been released after the previous allocation. 56 * In that case, a {@link #MISUSE_MESSAGE warning} is logged 57 * and the previously issued connection is revoked. 58 * <p> 59 * This class is derived from <code>SimpleHttpConnectionManager</code> 60 * in HttpClient 3. See there for original authors. 61 * </p> 62 * 63 * @author <a href="mailto:rolandw at apache.org">Roland Weber</a> 64 * @author <a href="mailto:becke@u.washington.edu">Michael Becke</a> 65 * 66 * 67 * <!-- empty lines to avoid svn diff problems --> 68 * @version $Revision: 673450 $ 69 * 70 * @since 4.0 71 */ 72public class SingleClientConnManager implements ClientConnectionManager { 73 74 private final Log log = LogFactory.getLog(getClass()); 75 76 /** The message to be logged on multiple allocation. */ 77 public final static String MISUSE_MESSAGE = 78 "Invalid use of SingleClientConnManager: connection still allocated.\n" + 79 "Make sure to release the connection before allocating another one."; 80 81 82 /** The schemes supported by this connection manager. */ 83 protected SchemeRegistry schemeRegistry; 84 85 /** The operator for opening and updating connections. */ 86 protected ClientConnectionOperator connOperator; 87 88 /** The one and only entry in this pool. */ 89 protected PoolEntry uniquePoolEntry; 90 91 /** The currently issued managed connection, if any. */ 92 protected ConnAdapter managedConn; 93 94 /** The time of the last connection release, or -1. */ 95 protected long lastReleaseTime; 96 97 /** The time the last released connection expires and shouldn't be reused. */ 98 protected long connectionExpiresTime; 99 100 /** Whether the connection should be shut down on release. */ 101 protected boolean alwaysShutDown; 102 103 /** Indicates whether this connection manager is shut down. */ 104 protected volatile boolean isShutDown; 105 106 107 108 109 /** 110 * Creates a new simple connection manager. 111 * 112 * @param params the parameters for this manager 113 * @param schreg the scheme registry, or 114 * <code>null</code> for the default registry 115 */ 116 public SingleClientConnManager(HttpParams params, 117 SchemeRegistry schreg) { 118 119 if (schreg == null) { 120 throw new IllegalArgumentException 121 ("Scheme registry must not be null."); 122 } 123 this.schemeRegistry = schreg; 124 this.connOperator = createConnectionOperator(schreg); 125 this.uniquePoolEntry = new PoolEntry(); 126 this.managedConn = null; 127 this.lastReleaseTime = -1L; 128 this.alwaysShutDown = false; //@@@ from params? as argument? 129 this.isShutDown = false; 130 131 } // <constructor> 132 133 134 @Override 135 protected void finalize() throws Throwable { 136 shutdown(); 137 super.finalize(); 138 } 139 140 141 // non-javadoc, see interface ClientConnectionManager 142 public SchemeRegistry getSchemeRegistry() { 143 return this.schemeRegistry; 144 } 145 146 147 /** 148 * Hook for creating the connection operator. 149 * It is called by the constructor. 150 * Derived classes can override this method to change the 151 * instantiation of the operator. 152 * The default implementation here instantiates 153 * {@link DefaultClientConnectionOperator DefaultClientConnectionOperator}. 154 * 155 * @param schreg the scheme registry to use, or <code>null</code> 156 * 157 * @return the connection operator to use 158 */ 159 protected ClientConnectionOperator 160 createConnectionOperator(SchemeRegistry schreg) { 161 162 return new DefaultClientConnectionOperator(schreg); 163 } 164 165 166 /** 167 * Asserts that this manager is not shut down. 168 * 169 * @throws IllegalStateException if this manager is shut down 170 */ 171 protected final void assertStillUp() 172 throws IllegalStateException { 173 174 if (this.isShutDown) 175 throw new IllegalStateException("Manager is shut down."); 176 } 177 178 179 public final ClientConnectionRequest requestConnection( 180 final HttpRoute route, 181 final Object state) { 182 183 return new ClientConnectionRequest() { 184 185 public void abortRequest() { 186 // Nothing to abort, since requests are immediate. 187 } 188 189 public ManagedClientConnection getConnection( 190 long timeout, TimeUnit tunit) { 191 return SingleClientConnManager.this.getConnection( 192 route, state); 193 } 194 195 }; 196 } 197 198 199 /** 200 * Obtains a connection. 201 * This method does not block. 202 * 203 * @param route where the connection should point to 204 * 205 * @return a connection that can be used to communicate 206 * along the given route 207 */ 208 public ManagedClientConnection getConnection(HttpRoute route, Object state) { 209 210 if (route == null) { 211 throw new IllegalArgumentException("Route may not be null."); 212 } 213 assertStillUp(); 214 215 if (log.isDebugEnabled()) { 216 log.debug("Get connection for route " + route); 217 } 218 219 if (managedConn != null) 220 revokeConnection(); 221 222 // check re-usability of the connection 223 boolean recreate = false; 224 boolean shutdown = false; 225 226 // Kill the connection if it expired. 227 closeExpiredConnections(); 228 229 if (uniquePoolEntry.connection.isOpen()) { 230 RouteTracker tracker = uniquePoolEntry.tracker; 231 shutdown = (tracker == null || // can happen if method is aborted 232 !tracker.toRoute().equals(route)); 233 } else { 234 // If the connection is not open, create a new PoolEntry, 235 // as the connection may have been marked not reusable, 236 // due to aborts -- and the PoolEntry should not be reused 237 // either. There's no harm in recreating an entry if 238 // the connection is closed. 239 recreate = true; 240 } 241 242 if (shutdown) { 243 recreate = true; 244 try { 245 uniquePoolEntry.shutdown(); 246 } catch (IOException iox) { 247 log.debug("Problem shutting down connection.", iox); 248 } 249 } 250 251 if (recreate) 252 uniquePoolEntry = new PoolEntry(); 253 254 // BEGIN android-changed 255 // When using a recycled Socket, we need to re-tag it with any 256 // updated statistics options. 257 try { 258 final Socket socket = uniquePoolEntry.connection.getSocket(); 259 if (socket != null) { 260 SocketTagger.get().tag(socket); 261 } 262 } catch (IOException iox) { 263 log.debug("Problem tagging socket.", iox); 264 } 265 // END android-changed 266 267 managedConn = new ConnAdapter(uniquePoolEntry, route); 268 269 return managedConn; 270 } 271 272 273 // non-javadoc, see interface ClientConnectionManager 274 public void releaseConnection(ManagedClientConnection conn, long validDuration, TimeUnit timeUnit) { 275 assertStillUp(); 276 277 if (!(conn instanceof ConnAdapter)) { 278 throw new IllegalArgumentException 279 ("Connection class mismatch, " + 280 "connection not obtained from this manager."); 281 } 282 283 if (log.isDebugEnabled()) { 284 log.debug("Releasing connection " + conn); 285 } 286 287 ConnAdapter sca = (ConnAdapter) conn; 288 if (sca.poolEntry == null) 289 return; // already released 290 ClientConnectionManager manager = sca.getManager(); 291 if (manager != null && manager != this) { 292 throw new IllegalArgumentException 293 ("Connection not obtained from this manager."); 294 } 295 296 try { 297 // BEGIN android-changed 298 // When recycling a Socket, we un-tag it to avoid collecting 299 // statistics from future users. 300 final Socket socket = uniquePoolEntry.connection.getSocket(); 301 if (socket != null) { 302 SocketTagger.get().untag(socket); 303 } 304 // END android-changed 305 306 // make sure that the response has been read completely 307 if (sca.isOpen() && (this.alwaysShutDown || 308 !sca.isMarkedReusable()) 309 ) { 310 if (log.isDebugEnabled()) { 311 log.debug 312 ("Released connection open but not reusable."); 313 } 314 315 // make sure this connection will not be re-used 316 // we might have gotten here because of a shutdown trigger 317 // shutdown of the adapter also clears the tracked route 318 sca.shutdown(); 319 } 320 } catch (IOException iox) { 321 //@@@ log as warning? let pass? 322 if (log.isDebugEnabled()) 323 log.debug("Exception shutting down released connection.", 324 iox); 325 } finally { 326 sca.detach(); 327 managedConn = null; 328 lastReleaseTime = System.currentTimeMillis(); 329 if(validDuration > 0) 330 connectionExpiresTime = timeUnit.toMillis(validDuration) + lastReleaseTime; 331 else 332 connectionExpiresTime = Long.MAX_VALUE; 333 } 334 } // releaseConnection 335 336 public void closeExpiredConnections() { 337 if(System.currentTimeMillis() >= connectionExpiresTime) { 338 closeIdleConnections(0, TimeUnit.MILLISECONDS); 339 } 340 } 341 342 343 // non-javadoc, see interface ClientConnectionManager 344 public void closeIdleConnections(long idletime, TimeUnit tunit) { 345 assertStillUp(); 346 347 // idletime can be 0 or negative, no problem there 348 if (tunit == null) { 349 throw new IllegalArgumentException("Time unit must not be null."); 350 } 351 352 if ((managedConn == null) && uniquePoolEntry.connection.isOpen()) { 353 final long cutoff = 354 System.currentTimeMillis() - tunit.toMillis(idletime); 355 if (lastReleaseTime <= cutoff) { 356 try { 357 uniquePoolEntry.close(); 358 } catch (IOException iox) { 359 // ignore 360 log.debug("Problem closing idle connection.", iox); 361 } 362 } 363 } 364 } 365 366 367 // non-javadoc, see interface ClientConnectionManager 368 public void shutdown() { 369 370 this.isShutDown = true; 371 372 if (managedConn != null) 373 managedConn.detach(); 374 375 try { 376 if (uniquePoolEntry != null) // and connection open? 377 uniquePoolEntry.shutdown(); 378 } catch (IOException iox) { 379 // ignore 380 log.debug("Problem while shutting down manager.", iox); 381 } finally { 382 uniquePoolEntry = null; 383 } 384 } 385 386 387 /** 388 * Revokes the currently issued connection. 389 * The adapter gets disconnected, the connection will be shut down. 390 */ 391 protected void revokeConnection() { 392 if (managedConn == null) 393 return; 394 395 log.warn(MISUSE_MESSAGE); 396 397 managedConn.detach(); 398 399 try { 400 uniquePoolEntry.shutdown(); 401 } catch (IOException iox) { 402 // ignore 403 log.debug("Problem while shutting down connection.", iox); 404 } 405 } 406 407 408 /** 409 * The pool entry for this connection manager. 410 */ 411 protected class PoolEntry extends AbstractPoolEntry { 412 413 /** 414 * Creates a new pool entry. 415 * 416 */ 417 protected PoolEntry() { 418 super(SingleClientConnManager.this.connOperator, null); 419 } 420 421 /** 422 * Closes the connection in this pool entry. 423 */ 424 protected void close() 425 throws IOException { 426 427 shutdownEntry(); 428 if (connection.isOpen()) 429 connection.close(); 430 } 431 432 433 /** 434 * Shuts down the connection in this pool entry. 435 */ 436 protected void shutdown() 437 throws IOException { 438 439 shutdownEntry(); 440 if (connection.isOpen()) 441 connection.shutdown(); 442 } 443 444 } // class PoolEntry 445 446 447 448 /** 449 * The connection adapter used by this manager. 450 */ 451 protected class ConnAdapter extends AbstractPooledConnAdapter { 452 453 /** 454 * Creates a new connection adapter. 455 * 456 * @param entry the pool entry for the connection being wrapped 457 * @param route the planned route for this connection 458 */ 459 protected ConnAdapter(PoolEntry entry, HttpRoute route) { 460 super(SingleClientConnManager.this, entry); 461 markReusable(); 462 entry.route = route; 463 } 464 465 } 466 467 468} // class SingleClientConnManager 469