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
1 /*
2 * ====================================================================
3 * Licensed to the Apache Software Foundation (ASF) under one
4 * or more contributor license agreements. See the NOTICE file
5 * distributed with this work for additional information
6 * regarding copyright ownership. The ASF licenses this file
7 * to you under the Apache License, Version 2.0 (the
8 * "License"); you may not use this file except in compliance
9 * with the License. You may obtain a copy of the License at
10 *
11 * http://www.apache.org/licenses/LICENSE-2.0
12 *
13 * Unless required by applicable law or agreed to in writing,
14 * software distributed under the License is distributed on an
15 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16 * KIND, either express or implied. See the License for the
17 * specific language governing permissions and limitations
18 * under the License.
19 * ====================================================================
20 *
21 * This software consists of voluntary contributions made by many
22 * individuals on behalf of the Apache Software Foundation. For more
23 * information on the Apache Software Foundation, please see
24 * <http://www.apache.org/>.
25 *
26 */
28 package ch.boye.httpclientandroidlib.impl.conn;
30 import java.io.IOException;
31 import java.util.concurrent.TimeUnit;
33 import ch.boye.httpclientandroidlib.annotation.GuardedBy;
34 import ch.boye.httpclientandroidlib.annotation.ThreadSafe;
36 import ch.boye.httpclientandroidlib.androidextra.HttpClientAndroidLog;
37 /* LogFactory removed by HttpClient for Android script. */
38 import ch.boye.httpclientandroidlib.conn.ClientConnectionManager;
39 import ch.boye.httpclientandroidlib.conn.ClientConnectionOperator;
40 import ch.boye.httpclientandroidlib.conn.ClientConnectionRequest;
41 import ch.boye.httpclientandroidlib.conn.ManagedClientConnection;
42 import ch.boye.httpclientandroidlib.conn.routing.HttpRoute;
43 import ch.boye.httpclientandroidlib.conn.routing.RouteTracker;
44 import ch.boye.httpclientandroidlib.conn.scheme.SchemeRegistry;
45 import ch.boye.httpclientandroidlib.params.HttpParams;
47 /**
48 * A connection manager for a single connection. This connection manager
49 * maintains only one active connection at a time. Even though this class
50 * is thread-safe it ought to be used by one execution thread only.
51 * <p>
52 * SingleClientConnManager will make an effort to reuse the connection
53 * for subsequent requests with the same {@link HttpRoute route}.
54 * It will, however, close the existing connection and open it
55 * for the given route, if the route of the persistent connection does
56 * not match that of the connection request. If the connection has been
57 * already been allocated {@link IllegalStateException} is thrown.
58 *
59 * @since 4.0
60 */
61 @ThreadSafe
62 public class SingleClientConnManager implements ClientConnectionManager {
64 public HttpClientAndroidLog log = new HttpClientAndroidLog(getClass());
66 /** The message to be logged on multiple allocation. */
67 public final static String MISUSE_MESSAGE =
68 "Invalid use of SingleClientConnManager: connection still allocated.\n" +
69 "Make sure to release the connection before allocating another one.";
71 /** The schemes supported by this connection manager. */
72 protected final SchemeRegistry schemeRegistry;
74 /** The operator for opening and updating connections. */
75 protected final ClientConnectionOperator connOperator;
77 /** Whether the connection should be shut down on release. */
78 protected final boolean alwaysShutDown;
80 /** The one and only entry in this pool. */
81 @GuardedBy("this")
82 protected PoolEntry uniquePoolEntry;
84 /** The currently issued managed connection, if any. */
85 @GuardedBy("this")
86 protected ConnAdapter managedConn;
88 /** The time of the last connection release, or -1. */
89 @GuardedBy("this")
90 protected long lastReleaseTime;
92 /** The time the last released connection expires and shouldn't be reused. */
93 @GuardedBy("this")
94 protected long connectionExpiresTime;
96 /** Indicates whether this connection manager is shut down. */
97 protected volatile boolean isShutDown;
99 /**
100 * Creates a new simple connection manager.
101 *
102 * @param params the parameters for this manager
103 * @param schreg the scheme registry
104 *
105 * @deprecated use {@link SingleClientConnManager#SingleClientConnManager(SchemeRegistry)}
106 */
107 @Deprecated
108 public SingleClientConnManager(HttpParams params,
109 SchemeRegistry schreg) {
110 this(schreg);
111 }
112 /**
113 * Creates a new simple connection manager.
114 *
115 * @param schreg the scheme registry
116 */
117 public SingleClientConnManager(final SchemeRegistry schreg) {
118 if (schreg == null) {
119 throw new IllegalArgumentException
120 ("Scheme registry must not be null.");
121 }
122 this.schemeRegistry = schreg;
123 this.connOperator = createConnectionOperator(schreg);
124 this.uniquePoolEntry = new PoolEntry();
125 this.managedConn = null;
126 this.lastReleaseTime = -1L;
127 this.alwaysShutDown = false; //@@@ from params? as argument?
128 this.isShutDown = false;
129 }
131 /**
132 * @since 4.1
133 */
134 public SingleClientConnManager() {
135 this(SchemeRegistryFactory.createDefault());
136 }
138 @Override
139 protected void finalize() throws Throwable {
140 try {
141 shutdown();
142 } finally { // Make sure we call overridden method even if shutdown barfs
143 super.finalize();
144 }
145 }
147 public SchemeRegistry getSchemeRegistry() {
148 return this.schemeRegistry;
149 }
151 /**
152 * Hook for creating the connection operator.
153 * It is called by the constructor.
154 * Derived classes can override this method to change the
155 * instantiation of the operator.
156 * The default implementation here instantiates
157 * {@link DefaultClientConnectionOperator DefaultClientConnectionOperator}.
158 *
159 * @param schreg the scheme registry to use, or <code>null</code>
160 *
161 * @return the connection operator to use
162 */
163 protected ClientConnectionOperator
164 createConnectionOperator(SchemeRegistry schreg) {
165 return new DefaultClientConnectionOperator(schreg);
166 }
168 /**
169 * Asserts that this manager is not shut down.
170 *
171 * @throws IllegalStateException if this manager is shut down
172 */
173 protected final void assertStillUp() throws IllegalStateException {
174 if (this.isShutDown)
175 throw new IllegalStateException("Manager is shut down.");
176 }
178 public final ClientConnectionRequest requestConnection(
179 final HttpRoute route,
180 final Object state) {
182 return new ClientConnectionRequest() {
184 public void abortRequest() {
185 // Nothing to abort, since requests are immediate.
186 }
188 public ManagedClientConnection getConnection(
189 long timeout, TimeUnit tunit) {
190 return SingleClientConnManager.this.getConnection(
191 route, state);
192 }
194 };
195 }
197 /**
198 * Obtains a connection.
199 *
200 * @param route where the connection should point to
201 *
202 * @return a connection that can be used to communicate
203 * along the given route
204 */
205 public synchronized ManagedClientConnection getConnection(HttpRoute route, Object state) {
206 if (route == null) {
207 throw new IllegalArgumentException("Route may not be null.");
208 }
209 assertStillUp();
211 if (log.isDebugEnabled()) {
212 log.debug("Get connection for route " + route);
213 }
215 if (managedConn != null)
216 throw new IllegalStateException(MISUSE_MESSAGE);
218 // check re-usability of the connection
219 boolean recreate = false;
220 boolean shutdown = false;
222 // Kill the connection if it expired.
223 closeExpiredConnections();
225 if (uniquePoolEntry.connection.isOpen()) {
226 RouteTracker tracker = uniquePoolEntry.tracker;
227 shutdown = (tracker == null || // can happen if method is aborted
228 !tracker.toRoute().equals(route));
229 } else {
230 // If the connection is not open, create a new PoolEntry,
231 // as the connection may have been marked not reusable,
232 // due to aborts -- and the PoolEntry should not be reused
233 // either. There's no harm in recreating an entry if
234 // the connection is closed.
235 recreate = true;
236 }
238 if (shutdown) {
239 recreate = true;
240 try {
241 uniquePoolEntry.shutdown();
242 } catch (IOException iox) {
243 log.debug("Problem shutting down connection.", iox);
244 }
245 }
247 if (recreate)
248 uniquePoolEntry = new PoolEntry();
250 managedConn = new ConnAdapter(uniquePoolEntry, route);
252 return managedConn;
253 }
255 public synchronized void releaseConnection(
256 ManagedClientConnection conn,
257 long validDuration, TimeUnit timeUnit) {
258 assertStillUp();
260 if (!(conn instanceof ConnAdapter)) {
261 throw new IllegalArgumentException
262 ("Connection class mismatch, " +
263 "connection not obtained from this manager.");
264 }
266 if (log.isDebugEnabled()) {
267 log.debug("Releasing connection " + conn);
268 }
270 ConnAdapter sca = (ConnAdapter) conn;
271 if (sca.poolEntry == null)
272 return; // already released
273 ClientConnectionManager manager = sca.getManager();
274 if (manager != null && manager != this) {
275 throw new IllegalArgumentException
276 ("Connection not obtained from this manager.");
277 }
279 try {
280 // make sure that the response has been read completely
281 if (sca.isOpen() && (this.alwaysShutDown ||
282 !sca.isMarkedReusable())
283 ) {
284 if (log.isDebugEnabled()) {
285 log.debug
286 ("Released connection open but not reusable.");
287 }
289 // make sure this connection will not be re-used
290 // we might have gotten here because of a shutdown trigger
291 // shutdown of the adapter also clears the tracked route
292 sca.shutdown();
293 }
294 } catch (IOException iox) {
295 if (log.isDebugEnabled())
296 log.debug("Exception shutting down released connection.",
297 iox);
298 } finally {
299 sca.detach();
300 managedConn = null;
301 lastReleaseTime = System.currentTimeMillis();
302 if(validDuration > 0)
303 connectionExpiresTime = timeUnit.toMillis(validDuration) + lastReleaseTime;
304 else
305 connectionExpiresTime = Long.MAX_VALUE;
306 }
307 }
309 public synchronized void closeExpiredConnections() {
310 if(System.currentTimeMillis() >= connectionExpiresTime) {
311 closeIdleConnections(0, TimeUnit.MILLISECONDS);
312 }
313 }
315 public synchronized void closeIdleConnections(long idletime, TimeUnit tunit) {
316 assertStillUp();
318 // idletime can be 0 or negative, no problem there
319 if (tunit == null) {
320 throw new IllegalArgumentException("Time unit must not be null.");
321 }
323 if ((managedConn == null) && uniquePoolEntry.connection.isOpen()) {
324 final long cutoff =
325 System.currentTimeMillis() - tunit.toMillis(idletime);
326 if (lastReleaseTime <= cutoff) {
327 try {
328 uniquePoolEntry.close();
329 } catch (IOException iox) {
330 // ignore
331 log.debug("Problem closing idle connection.", iox);
332 }
333 }
334 }
335 }
337 public synchronized void shutdown() {
339 this.isShutDown = true;
341 if (managedConn != null)
342 managedConn.detach();
344 try {
345 if (uniquePoolEntry != null) // and connection open?
346 uniquePoolEntry.shutdown();
347 } catch (IOException iox) {
348 // ignore
349 log.debug("Problem while shutting down manager.", iox);
350 } finally {
351 uniquePoolEntry = null;
352 }
353 }
355 /**
356 * @deprecated no longer used
357 */
358 @Deprecated
359 protected synchronized void revokeConnection() {
360 if (managedConn == null)
361 return;
362 managedConn.detach();
363 try {
364 uniquePoolEntry.shutdown();
365 } catch (IOException iox) {
366 // ignore
367 log.debug("Problem while shutting down connection.", iox);
368 }
369 }
371 /**
372 * The pool entry for this connection manager.
373 */
374 protected class PoolEntry extends AbstractPoolEntry {
376 /**
377 * Creates a new pool entry.
378 *
379 */
380 protected PoolEntry() {
381 super(SingleClientConnManager.this.connOperator, null);
382 }
384 /**
385 * Closes the connection in this pool entry.
386 */
387 protected void close() throws IOException {
388 shutdownEntry();
389 if (connection.isOpen())
390 connection.close();
391 }
393 /**
394 * Shuts down the connection in this pool entry.
395 */
396 protected void shutdown() throws IOException {
397 shutdownEntry();
398 if (connection.isOpen())
399 connection.shutdown();
400 }
402 }
404 /**
405 * The connection adapter used by this manager.
406 */
407 protected class ConnAdapter extends AbstractPooledConnAdapter {
409 /**
410 * Creates a new connection adapter.
411 *
412 * @param entry the pool entry for the connection being wrapped
413 * @param route the planned route for this connection
414 */
415 protected ConnAdapter(PoolEntry entry, HttpRoute route) {
416 super(SingleClientConnManager.this, entry);
417 markReusable();
418 entry.route = route;
419 }
421 }
423 }