1/* 2 * Licensed to the Apache Software Foundation (ASF) under one or more 3 * contributor license agreements. See the NOTICE file distributed with 4 * this work for additional information regarding copyright ownership. 5 * The ASF licenses this file to You under the Apache License, Version 2.0 6 * (the "License"); you may not use this file except in compliance with 7 * the License. You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 */ 17package com.squareup.okhttp; 18 19import com.squareup.okhttp.internal.Platform; 20import com.squareup.okhttp.internal.Util; 21import java.net.SocketException; 22import java.util.ArrayList; 23import java.util.LinkedList; 24import java.util.List; 25import java.util.ListIterator; 26import java.util.concurrent.ExecutorService; 27import java.util.concurrent.LinkedBlockingQueue; 28import java.util.concurrent.ThreadPoolExecutor; 29import java.util.concurrent.TimeUnit; 30 31/** 32 * Manages reuse of HTTP and SPDY connections for reduced network latency. HTTP 33 * requests that share the same {@link com.squareup.okhttp.Address} may share a 34 * {@link com.squareup.okhttp.Connection}. This class implements the policy of 35 * which connections to keep open for future use. 36 * 37 * <p>The {@link #getDefault() system-wide default} uses system properties for 38 * tuning parameters: 39 * <ul> 40 * <li>{@code http.keepAlive} true if HTTP and SPDY connections should be 41 * pooled at all. Default is true. 42 * <li>{@code http.maxConnections} maximum number of idle connections to 43 * each to keep in the pool. Default is 5. 44 * <li>{@code http.keepAliveDuration} Time in milliseconds to keep the 45 * connection alive in the pool before closing it. Default is 5 minutes. 46 * This property isn't used by {@code HttpURLConnection}. 47 * </ul> 48 * 49 * <p>The default instance <i>doesn't</i> adjust its configuration as system 50 * properties are changed. This assumes that the applications that set these 51 * parameters do so before making HTTP connections, and that this class is 52 * initialized lazily. 53 */ 54public class ConnectionPool { 55 private static final int MAX_CONNECTIONS_TO_CLEANUP = 2; 56 private static final long DEFAULT_KEEP_ALIVE_DURATION_MS = 5 * 60 * 1000; // 5 min 57 58 private static final ConnectionPool systemDefault; 59 60 static { 61 String keepAlive = System.getProperty("http.keepAlive"); 62 String keepAliveDuration = System.getProperty("http.keepAliveDuration"); 63 String maxIdleConnections = System.getProperty("http.maxConnections"); 64 long keepAliveDurationMs = keepAliveDuration != null ? Long.parseLong(keepAliveDuration) 65 : DEFAULT_KEEP_ALIVE_DURATION_MS; 66 if (keepAlive != null && !Boolean.parseBoolean(keepAlive)) { 67 systemDefault = new ConnectionPool(0, keepAliveDurationMs); 68 } else if (maxIdleConnections != null) { 69 systemDefault = new ConnectionPool(Integer.parseInt(maxIdleConnections), keepAliveDurationMs); 70 } else { 71 systemDefault = new ConnectionPool(5, keepAliveDurationMs); 72 } 73 } 74 75 /** The maximum number of idle connections for each address. */ 76 private final int maxIdleConnections; 77 private final long keepAliveDurationNs; 78 79 private final LinkedList<Connection> connections = new LinkedList<Connection>(); 80 81 /** We use a single background thread to cleanup expired connections. */ 82 private final ExecutorService executorService = new ThreadPoolExecutor(0, 1, 83 60L, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>(), 84 Util.threadFactory("OkHttp ConnectionPool", true)); 85 86 private enum CleanMode { 87 /** 88 * Connection clean up is driven by usage of the pool. Each usage of the pool can schedule a 89 * clean up. A pool left in this state and unused may contain idle connections indefinitely. 90 */ 91 NORMAL, 92 /** 93 * Entered when a pool has been orphaned and is not expected to receive more usage, except for 94 * references held by existing connections. See {@link #enterDrainMode()}. 95 * A thread runs periodically to close idle connections in the pool until the pool is empty and 96 * then the state moves to {@link #DRAINED}. 97 */ 98 DRAINING, 99 /** 100 * The pool is empty and no clean-up is taking place. Connections may still be added to the 101 * pool due to latent references to the pool, in which case the pool re-enters 102 * {@link #DRAINING}. If the pool is DRAINED and no longer referenced it is safe to be garbage 103 * collected. 104 */ 105 DRAINED 106 } 107 /** The current mode for cleaning connections in the pool */ 108 private CleanMode cleanMode = CleanMode.NORMAL; 109 110 // A scheduled drainModeRunnable keeps a reference to the enclosing ConnectionPool, 111 // preventing the ConnectionPool from being garbage collected before all held connections have 112 // been explicitly closed. If this was not the case any open connections in the pool would trigger 113 // StrictMode violations in Android when they were garbage collected. http://b/18369687 114 private final Runnable drainModeRunnable = new Runnable() { 115 @Override public void run() { 116 // Close any connections we can. 117 connectionsCleanupRunnable.run(); 118 119 synchronized (ConnectionPool.this) { 120 // See whether we should continue checking the connection pool. 121 if (connections.size() > 0) { 122 // Pause to avoid checking too regularly, which would drain the battery on mobile 123 // devices. The wait() surrenders the pool monitor and will not block other calls. 124 try { 125 // Use the keep alive duration as a rough indicator of a good check interval. 126 long keepAliveDurationMillis = keepAliveDurationNs / (1000 * 1000); 127 ConnectionPool.this.wait(keepAliveDurationMillis); 128 } catch (InterruptedException e) { 129 // Ignored. 130 } 131 132 // Reschedule "this" to perform another clean-up. 133 executorService.execute(this); 134 } else { 135 cleanMode = CleanMode.DRAINED; 136 } 137 } 138 } 139 }; 140 141 private final Runnable connectionsCleanupRunnable = new Runnable() { 142 @Override public void run() { 143 List<Connection> expiredConnections = new ArrayList<Connection>(MAX_CONNECTIONS_TO_CLEANUP); 144 int idleConnectionCount = 0; 145 synchronized (ConnectionPool.this) { 146 for (ListIterator<Connection> i = connections.listIterator(connections.size()); 147 i.hasPrevious(); ) { 148 Connection connection = i.previous(); 149 if (!connection.isAlive() || connection.isExpired(keepAliveDurationNs)) { 150 i.remove(); 151 expiredConnections.add(connection); 152 if (expiredConnections.size() == MAX_CONNECTIONS_TO_CLEANUP) break; 153 } else if (connection.isIdle()) { 154 idleConnectionCount++; 155 } 156 } 157 158 for (ListIterator<Connection> i = connections.listIterator(connections.size()); 159 i.hasPrevious() && idleConnectionCount > maxIdleConnections; ) { 160 Connection connection = i.previous(); 161 if (connection.isIdle()) { 162 expiredConnections.add(connection); 163 i.remove(); 164 --idleConnectionCount; 165 } 166 } 167 } 168 for (Connection expiredConnection : expiredConnections) { 169 Util.closeQuietly(expiredConnection); 170 } 171 } 172 }; 173 174 public ConnectionPool(int maxIdleConnections, long keepAliveDurationMs) { 175 this.maxIdleConnections = maxIdleConnections; 176 this.keepAliveDurationNs = keepAliveDurationMs * 1000 * 1000; 177 } 178 179 /** 180 * Returns a snapshot of the connections in this pool, ordered from newest to 181 * oldest. Waits for the cleanup callable to run if it is currently scheduled. 182 * Only use in tests. 183 */ 184 List<Connection> getConnections() { 185 waitForCleanupCallableToRun(); 186 synchronized (this) { 187 return new ArrayList<Connection>(connections); 188 } 189 } 190 191 /** 192 * Blocks until the executor service has processed all currently enqueued 193 * jobs. 194 */ 195 private void waitForCleanupCallableToRun() { 196 try { 197 executorService.submit(new Runnable() { 198 @Override public void run() { 199 } 200 }).get(); 201 } catch (Exception e) { 202 throw new AssertionError(); 203 } 204 } 205 206 public static ConnectionPool getDefault() { 207 return systemDefault; 208 } 209 210 /** Returns total number of connections in the pool. */ 211 public synchronized int getConnectionCount() { 212 return connections.size(); 213 } 214 215 /** Returns total number of spdy connections in the pool. */ 216 public synchronized int getSpdyConnectionCount() { 217 int total = 0; 218 for (Connection connection : connections) { 219 if (connection.isSpdy()) total++; 220 } 221 return total; 222 } 223 224 /** Returns total number of http connections in the pool. */ 225 public synchronized int getHttpConnectionCount() { 226 int total = 0; 227 for (Connection connection : connections) { 228 if (!connection.isSpdy()) total++; 229 } 230 return total; 231 } 232 233 /** Returns a recycled connection to {@code address}, or null if no such connection exists. */ 234 public synchronized Connection get(Address address) { 235 Connection foundConnection = null; 236 for (ListIterator<Connection> i = connections.listIterator(connections.size()); 237 i.hasPrevious(); ) { 238 Connection connection = i.previous(); 239 if (!connection.getRoute().getAddress().equals(address) 240 || !connection.isAlive() 241 || System.nanoTime() - connection.getIdleStartTimeNs() >= keepAliveDurationNs) { 242 continue; 243 } 244 i.remove(); 245 if (!connection.isSpdy()) { 246 try { 247 Platform.get().tagSocket(connection.getSocket()); 248 } catch (SocketException e) { 249 Util.closeQuietly(connection); 250 // When unable to tag, skip recycling and close 251 Platform.get().logW("Unable to tagSocket(): " + e); 252 continue; 253 } 254 } 255 foundConnection = connection; 256 break; 257 } 258 259 if (foundConnection != null && foundConnection.isSpdy()) { 260 connections.addFirst(foundConnection); // Add it back after iteration. 261 } 262 263 scheduleCleanupAsRequired(); 264 return foundConnection; 265 } 266 267 /** 268 * Gives {@code connection} to the pool. The pool may store the connection, 269 * or close it, as its policy describes. 270 * 271 * <p>It is an error to use {@code connection} after calling this method. 272 */ 273 public void recycle(Connection connection) { 274 if (connection.isSpdy()) { 275 return; 276 } 277 278 if (!connection.clearOwner()) { 279 return; // This connection isn't eligible for reuse. 280 } 281 282 if (!connection.isAlive()) { 283 Util.closeQuietly(connection); 284 return; 285 } 286 287 try { 288 Platform.get().untagSocket(connection.getSocket()); 289 } catch (SocketException e) { 290 // When unable to remove tagging, skip recycling and close. 291 Platform.get().logW("Unable to untagSocket(): " + e); 292 Util.closeQuietly(connection); 293 return; 294 } 295 296 synchronized (this) { 297 connections.addFirst(connection); 298 connection.incrementRecycleCount(); 299 connection.resetIdleStartTime(); 300 scheduleCleanupAsRequired(); 301 } 302 303 } 304 305 /** 306 * Shares the SPDY connection with the pool. Callers to this method may 307 * continue to use {@code connection}. 308 */ 309 public void share(Connection connection) { 310 if (!connection.isSpdy()) throw new IllegalArgumentException(); 311 if (connection.isAlive()) { 312 synchronized (this) { 313 connections.addFirst(connection); 314 scheduleCleanupAsRequired(); 315 } 316 } 317 } 318 319 /** Close and remove all connections in the pool. */ 320 public void evictAll() { 321 List<Connection> connections; 322 synchronized (this) { 323 connections = new ArrayList<Connection>(this.connections); 324 this.connections.clear(); 325 } 326 327 for (int i = 0, size = connections.size(); i < size; i++) { 328 Util.closeQuietly(connections.get(i)); 329 } 330 } 331 332 /** 333 * A less abrupt way of draining the pool than {@link #evictAll()}. For use when the pool 334 * may still be referenced by active shared connections which cannot safely be closed. 335 */ 336 public void enterDrainMode() { 337 synchronized(this) { 338 cleanMode = CleanMode.DRAINING; 339 executorService.execute(drainModeRunnable); 340 } 341 } 342 343 public boolean isDrained() { 344 synchronized(this) { 345 return cleanMode == CleanMode.DRAINED; 346 } 347 } 348 349 // Callers must synchronize on "this". 350 private void scheduleCleanupAsRequired() { 351 switch (cleanMode) { 352 case NORMAL: 353 executorService.execute(connectionsCleanupRunnable); 354 break; 355 case DRAINING: 356 // Do nothing -drainModeRunnable is already scheduled, and will reschedules itself as 357 // needed. 358 break; 359 case DRAINED: 360 // A new connection has potentially been offered up to a drained pool. Restart the drain. 361 cleanMode = CleanMode.DRAINING; 362 executorService.execute(drainModeRunnable); 363 break; 364 } 365 } 366} 367