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 */ 17 package com.squareup.okhttp; 18 19 import com.squareup.okhttp.internal.Platform; 20 import com.squareup.okhttp.internal.Util; 21 import java.net.SocketException; 22 import java.util.ArrayList; 23 import java.util.LinkedList; 24 import java.util.List; 25 import java.util.ListIterator; 26 import java.util.concurrent.Executor; 27 import java.util.concurrent.LinkedBlockingQueue; 28 import java.util.concurrent.ThreadPoolExecutor; 29 import 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 */ 54 public final class ConnectionPool { 55 private static final long DEFAULT_KEEP_ALIVE_DURATION_MS = 5 * 60 * 1000; // 5 min 56 57 private static final ConnectionPool systemDefault; 58 59 static { 60 String keepAlive = System.getProperty("http.keepAlive"); 61 String keepAliveDuration = System.getProperty("http.keepAliveDuration"); 62 String maxIdleConnections = System.getProperty("http.maxConnections"); 63 long keepAliveDurationMs = keepAliveDuration != null ? Long.parseLong(keepAliveDuration) 64 : DEFAULT_KEEP_ALIVE_DURATION_MS; 65 if (keepAlive != null && !Boolean.parseBoolean(keepAlive)) { 66 systemDefault = new ConnectionPool(0, keepAliveDurationMs); 67 } else if (maxIdleConnections != null) { 68 systemDefault = new ConnectionPool(Integer.parseInt(maxIdleConnections), keepAliveDurationMs); 69 } else { 70 systemDefault = new ConnectionPool(5, keepAliveDurationMs); 71 } 72 } 73 74 /** The maximum number of idle connections for each address. */ 75 private final int maxIdleConnections; 76 private final long keepAliveDurationNs; 77 78 private final LinkedList<Connection> connections = new LinkedList<>(); 79 80 /** 81 * A background thread is used to cleanup expired connections. There will be, at most, a single 82 * thread running per connection pool. 83 * 84 * <p>A {@link ThreadPoolExecutor} is used and not a 85 * {@link java.util.concurrent.ScheduledThreadPoolExecutor}; ScheduledThreadPoolExecutors do not 86 * shrink. This executor shrinks the thread pool after a period of inactivity, and starts threads 87 * as needed. Delays are instead handled by the {@link #connectionsCleanupRunnable}. It is 88 * important that the {@link #connectionsCleanupRunnable} stops eventually, otherwise it will pin 89 * the thread, and thus the connection pool, in memory. 90 */ 91 private Executor executor = new ThreadPoolExecutor( 92 0 /* corePoolSize */, 1 /* maximumPoolSize */, 60L /* keepAliveTime */, TimeUnit.SECONDS, 93 new LinkedBlockingQueue<Runnable>(), Util.threadFactory("OkHttp ConnectionPool", true)); 94 95 private final Runnable connectionsCleanupRunnable = new Runnable() { 96 @Override public void run() { 97 runCleanupUntilPoolIsEmpty(); 98 } 99 }; 100 101 public ConnectionPool(int maxIdleConnections, long keepAliveDurationMs) { 102 this.maxIdleConnections = maxIdleConnections; 103 this.keepAliveDurationNs = keepAliveDurationMs * 1000 * 1000; 104 } 105 106 public static ConnectionPool getDefault() { 107 return systemDefault; 108 } 109 110 /** Returns total number of connections in the pool. */ 111 public synchronized int getConnectionCount() { 112 return connections.size(); 113 } 114 115 /** @deprecated Use {@link #getMultiplexedConnectionCount()}. */ 116 @Deprecated 117 public synchronized int getSpdyConnectionCount() { 118 return getMultiplexedConnectionCount(); 119 } 120 121 /** Returns total number of multiplexed connections in the pool. */ 122 public synchronized int getMultiplexedConnectionCount() { 123 int total = 0; 124 for (Connection connection : connections) { 125 if (connection.isSpdy()) total++; 126 } 127 return total; 128 } 129 130 /** Returns total number of http connections in the pool. */ 131 public synchronized int getHttpConnectionCount() { 132 return connections.size() - getMultiplexedConnectionCount(); 133 } 134 135 /** Returns a recycled connection to {@code address}, or null if no such connection exists. */ 136 public synchronized Connection get(Address address) { 137 Connection foundConnection = null; 138 for (ListIterator<Connection> i = connections.listIterator(connections.size()); 139 i.hasPrevious(); ) { 140 Connection connection = i.previous(); 141 if (!connection.getRoute().getAddress().equals(address) 142 || !connection.isAlive() 143 || System.nanoTime() - connection.getIdleStartTimeNs() >= keepAliveDurationNs) { 144 continue; 145 } 146 i.remove(); 147 if (!connection.isSpdy()) { 148 try { 149 Platform.get().tagSocket(connection.getSocket()); 150 } catch (SocketException e) { 151 Util.closeQuietly(connection.getSocket()); 152 // When unable to tag, skip recycling and close 153 Platform.get().logW("Unable to tagSocket(): " + e); 154 continue; 155 } 156 } 157 foundConnection = connection; 158 break; 159 } 160 161 if (foundConnection != null && foundConnection.isSpdy()) { 162 connections.addFirst(foundConnection); // Add it back after iteration. 163 } 164 165 return foundConnection; 166 } 167 168 /** 169 * Gives {@code connection} to the pool. The pool may store the connection, 170 * or close it, as its policy describes. 171 * 172 * <p>It is an error to use {@code connection} after calling this method. 173 */ 174 void recycle(Connection connection) { 175 if (connection.isSpdy()) { 176 return; 177 } 178 179 if (!connection.clearOwner()) { 180 return; // This connection isn't eligible for reuse. 181 } 182 183 if (!connection.isAlive()) { 184 Util.closeQuietly(connection.getSocket()); 185 return; 186 } 187 188 try { 189 Platform.get().untagSocket(connection.getSocket()); 190 } catch (SocketException e) { 191 // When unable to remove tagging, skip recycling and close. 192 Platform.get().logW("Unable to untagSocket(): " + e); 193 Util.closeQuietly(connection.getSocket()); 194 return; 195 } 196 197 synchronized (this) { 198 addConnection(connection); 199 connection.incrementRecycleCount(); 200 connection.resetIdleStartTime(); 201 } 202 } 203 204 private void addConnection(Connection connection) { 205 boolean empty = connections.isEmpty(); 206 connections.addFirst(connection); 207 if (empty) { 208 executor.execute(connectionsCleanupRunnable); 209 } else { 210 notifyAll(); 211 } 212 } 213 214 /** 215 * Shares the SPDY connection with the pool. Callers to this method may 216 * continue to use {@code connection}. 217 */ 218 void share(Connection connection) { 219 if (!connection.isSpdy()) throw new IllegalArgumentException(); 220 if (!connection.isAlive()) return; 221 synchronized (this) { 222 addConnection(connection); 223 } 224 } 225 226 /** Close and remove all connections in the pool. */ 227 public void evictAll() { 228 List<Connection> toEvict; 229 synchronized (this) { 230 toEvict = new ArrayList<>(connections); 231 connections.clear(); 232 notifyAll(); 233 } 234 235 for (int i = 0, size = toEvict.size(); i < size; i++) { 236 Util.closeQuietly(toEvict.get(i).getSocket()); 237 } 238 } 239 240 private void runCleanupUntilPoolIsEmpty() { 241 while (true) { 242 if (!performCleanup()) return; // Halt cleanup. 243 } 244 } 245 246 /** 247 * Attempts to make forward progress on connection eviction. There are three possible outcomes: 248 * 249 * <h3>The pool is empty.</h3> 250 * In this case, this method returns false and the eviction job should exit because there are no 251 * further cleanup tasks coming. (If additional connections are added to the pool, another cleanup 252 * job must be enqueued.) 253 * 254 * <h3>Connections were evicted.</h3> 255 * At least one connections was eligible for immediate eviction and was evicted. The method 256 * returns true and cleanup should continue. 257 * 258 * <h3>We waited to evict.</h3> 259 * None of the pooled connections were eligible for immediate eviction. Instead, we waited until 260 * either a connection became eligible for eviction, or the connections list changed. In either 261 * case, the method returns true and cleanup should continue. 262 */ 263 // VisibleForTesting 264 boolean performCleanup() { 265 List<Connection> evictableConnections; 266 267 synchronized (this) { 268 if (connections.isEmpty()) return false; // Halt cleanup. 269 270 evictableConnections = new ArrayList<>(); 271 int idleConnectionCount = 0; 272 long now = System.nanoTime(); 273 long nanosUntilNextEviction = keepAliveDurationNs; 274 275 // Collect connections eligible for immediate eviction. 276 for (ListIterator<Connection> i = connections.listIterator(connections.size()); 277 i.hasPrevious(); ) { 278 Connection connection = i.previous(); 279 long nanosUntilEviction = connection.getIdleStartTimeNs() + keepAliveDurationNs - now; 280 if (nanosUntilEviction <= 0 || !connection.isAlive()) { 281 i.remove(); 282 evictableConnections.add(connection); 283 } else if (connection.isIdle()) { 284 idleConnectionCount++; 285 nanosUntilNextEviction = Math.min(nanosUntilNextEviction, nanosUntilEviction); 286 } 287 } 288 289 // If the pool has too many idle connections, gather more! Oldest to newest. 290 for (ListIterator<Connection> i = connections.listIterator(connections.size()); 291 i.hasPrevious() && idleConnectionCount > maxIdleConnections; ) { 292 Connection connection = i.previous(); 293 if (connection.isIdle()) { 294 evictableConnections.add(connection); 295 i.remove(); 296 --idleConnectionCount; 297 } 298 } 299 300 // If there's nothing to evict, wait. (This will be interrupted if connections are added.) 301 if (evictableConnections.isEmpty()) { 302 try { 303 long millisUntilNextEviction = nanosUntilNextEviction / (1000 * 1000); 304 long remainderNanos = nanosUntilNextEviction - millisUntilNextEviction * (1000 * 1000); 305 this.wait(millisUntilNextEviction, (int) remainderNanos); 306 return true; // Cleanup continues. 307 } catch (InterruptedException ignored) { 308 } 309 } 310 } 311 312 // Actually do the eviction. Note that we avoid synchronized() when closing sockets. 313 for (int i = 0, size = evictableConnections.size(); i < size; i++) { 314 Connection expiredConnection = evictableConnections.get(i); 315 Util.closeQuietly(expiredConnection.getSocket()); 316 } 317 318 return true; // Cleanup continues. 319 } 320 321 /** 322 * Replace the default {@link Executor} with a different one. Only use in tests. 323 */ 324 // VisibleForTesting 325 void replaceCleanupExecutorForTests(Executor cleanupExecutor) { 326 this.executor = cleanupExecutor; 327 } 328 329 /** 330 * Returns a snapshot of the connections in this pool, ordered from newest to 331 * oldest. Only use in tests. 332 */ 333 // VisibleForTesting 334 synchronized List<Connection> getConnections() { 335 return new ArrayList<>(connections); 336 } 337 } 338