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.Callable; 27 import java.util.concurrent.ExecutorService; 28 import java.util.concurrent.LinkedBlockingQueue; 29 import java.util.concurrent.ThreadPoolExecutor; 30 import java.util.concurrent.TimeUnit; 31 32 /** 33 * Manages reuse of HTTP and SPDY connections for reduced network latency. HTTP 34 * requests that share the same {@link com.squareup.okhttp.Address} may share a 35 * {@link com.squareup.okhttp.Connection}. This class implements the policy of 36 * which connections to keep open for future use. 37 * 38 * <p>The {@link #getDefault() system-wide default} uses system properties for 39 * tuning parameters: 40 * <ul> 41 * <li>{@code http.keepAlive} true if HTTP and SPDY connections should be 42 * pooled at all. Default is true. 43 * <li>{@code http.maxConnections} maximum number of idle connections to 44 * each to keep in the pool. Default is 5. 45 * <li>{@code http.keepAliveDuration} Time in milliseconds to keep the 46 * connection alive in the pool before closing it. Default is 5 minutes. 47 * This property isn't used by {@code HttpURLConnection}. 48 * </ul> 49 * 50 * <p>The default instance <i>doesn't</i> adjust its configuration as system 51 * properties are changed. This assumes that the applications that set these 52 * parameters do so before making HTTP connections, and that this class is 53 * initialized lazily. 54 */ 55 public class ConnectionPool { 56 private static final int MAX_CONNECTIONS_TO_CLEANUP = 2; 57 private static final long DEFAULT_KEEP_ALIVE_DURATION_MS = 5 * 60 * 1000; // 5 min 58 59 private static final ConnectionPool systemDefault; 60 61 static { 62 String keepAlive = System.getProperty("http.keepAlive"); 63 String keepAliveDuration = System.getProperty("http.keepAliveDuration"); 64 String maxIdleConnections = System.getProperty("http.maxConnections"); 65 long keepAliveDurationMs = keepAliveDuration != null ? Long.parseLong(keepAliveDuration) 66 : DEFAULT_KEEP_ALIVE_DURATION_MS; 67 if (keepAlive != null && !Boolean.parseBoolean(keepAlive)) { 68 systemDefault = new ConnectionPool(0, keepAliveDurationMs); 69 } else if (maxIdleConnections != null) { 70 systemDefault = new ConnectionPool(Integer.parseInt(maxIdleConnections), keepAliveDurationMs); 71 } else { 72 systemDefault = new ConnectionPool(5, keepAliveDurationMs); 73 } 74 } 75 76 /** The maximum number of idle connections for each address. */ 77 private final int maxIdleConnections; 78 private final long keepAliveDurationNs; 79 80 private final LinkedList<Connection> connections = new LinkedList<Connection>(); 81 82 /** We use a single background thread to cleanup expired connections. */ 83 private final ExecutorService executorService = new ThreadPoolExecutor(0, 1, 84 60L, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>(), 85 Util.daemonThreadFactory("OkHttp ConnectionPool")); 86 private final Callable<Void> connectionsCleanupCallable = new Callable<Void>() { 87 @Override public Void call() throws Exception { 88 List<Connection> expiredConnections = new ArrayList<Connection>(MAX_CONNECTIONS_TO_CLEANUP); 89 int idleConnectionCount = 0; 90 synchronized (ConnectionPool.this) { 91 for (ListIterator<Connection> i = connections.listIterator(connections.size()); 92 i.hasPrevious(); ) { 93 Connection connection = i.previous(); 94 if (!connection.isAlive() || connection.isExpired(keepAliveDurationNs)) { 95 i.remove(); 96 expiredConnections.add(connection); 97 if (expiredConnections.size() == MAX_CONNECTIONS_TO_CLEANUP) break; 98 } else if (connection.isIdle()) { 99 idleConnectionCount++; 100 } 101 } 102 103 for (ListIterator<Connection> i = connections.listIterator(connections.size()); 104 i.hasPrevious() && idleConnectionCount > maxIdleConnections; ) { 105 Connection connection = i.previous(); 106 if (connection.isIdle()) { 107 expiredConnections.add(connection); 108 i.remove(); 109 --idleConnectionCount; 110 } 111 } 112 } 113 for (Connection expiredConnection : expiredConnections) { 114 Util.closeQuietly(expiredConnection); 115 } 116 return null; 117 } 118 }; 119 120 public ConnectionPool(int maxIdleConnections, long keepAliveDurationMs) { 121 this.maxIdleConnections = maxIdleConnections; 122 this.keepAliveDurationNs = keepAliveDurationMs * 1000 * 1000; 123 } 124 125 /** 126 * Returns a snapshot of the connections in this pool, ordered from newest to 127 * oldest. Waits for the cleanup callable to run if it is currently scheduled. 128 */ 129 List<Connection> getConnections() { 130 waitForCleanupCallableToRun(); 131 synchronized (this) { 132 return new ArrayList<Connection>(connections); 133 } 134 } 135 136 /** 137 * Blocks until the executor service has processed all currently enqueued 138 * jobs. 139 */ 140 private void waitForCleanupCallableToRun() { 141 try { 142 executorService.submit(new Runnable() { 143 @Override public void run() { 144 } 145 }).get(); 146 } catch (Exception e) { 147 throw new AssertionError(); 148 } 149 } 150 151 public static ConnectionPool getDefault() { 152 return systemDefault; 153 } 154 155 /** Returns total number of connections in the pool. */ 156 public synchronized int getConnectionCount() { 157 return connections.size(); 158 } 159 160 /** Returns total number of spdy connections in the pool. */ 161 public synchronized int getSpdyConnectionCount() { 162 int total = 0; 163 for (Connection connection : connections) { 164 if (connection.isSpdy()) total++; 165 } 166 return total; 167 } 168 169 /** Returns total number of http connections in the pool. */ 170 public synchronized int getHttpConnectionCount() { 171 int total = 0; 172 for (Connection connection : connections) { 173 if (!connection.isSpdy()) total++; 174 } 175 return total; 176 } 177 178 /** Returns a recycled connection to {@code address}, or null if no such connection exists. */ 179 public synchronized Connection get(Address address) { 180 Connection foundConnection = null; 181 for (ListIterator<Connection> i = connections.listIterator(connections.size()); 182 i.hasPrevious(); ) { 183 Connection connection = i.previous(); 184 if (!connection.getRoute().getAddress().equals(address) 185 || !connection.isAlive() 186 || System.nanoTime() - connection.getIdleStartTimeNs() >= keepAliveDurationNs) { 187 continue; 188 } 189 i.remove(); 190 if (!connection.isSpdy()) { 191 try { 192 Platform.get().tagSocket(connection.getSocket()); 193 } catch (SocketException e) { 194 Util.closeQuietly(connection); 195 // When unable to tag, skip recycling and close 196 Platform.get().logW("Unable to tagSocket(): " + e); 197 continue; 198 } 199 } 200 foundConnection = connection; 201 break; 202 } 203 204 if (foundConnection != null && foundConnection.isSpdy()) { 205 connections.addFirst(foundConnection); // Add it back after iteration. 206 } 207 208 executorService.submit(connectionsCleanupCallable); 209 return foundConnection; 210 } 211 212 /** 213 * Gives {@code connection} to the pool. The pool may store the connection, 214 * or close it, as its policy describes. 215 * 216 * <p>It is an error to use {@code connection} after calling this method. 217 */ 218 public void recycle(Connection connection) { 219 if (connection.isSpdy()) { 220 return; 221 } 222 223 if (!connection.isAlive()) { 224 Util.closeQuietly(connection); 225 return; 226 } 227 228 try { 229 Platform.get().untagSocket(connection.getSocket()); 230 } catch (SocketException e) { 231 // When unable to remove tagging, skip recycling and close. 232 Platform.get().logW("Unable to untagSocket(): " + e); 233 Util.closeQuietly(connection); 234 return; 235 } 236 237 synchronized (this) { 238 connections.addFirst(connection); 239 connection.resetIdleStartTime(); 240 } 241 242 executorService.submit(connectionsCleanupCallable); 243 } 244 245 /** 246 * Shares the SPDY connection with the pool. Callers to this method may 247 * continue to use {@code connection}. 248 */ 249 public void maybeShare(Connection connection) { 250 executorService.submit(connectionsCleanupCallable); 251 if (!connection.isSpdy()) { 252 // Only SPDY connections are sharable. 253 return; 254 } 255 if (connection.isAlive()) { 256 synchronized (this) { 257 connections.addFirst(connection); 258 } 259 } 260 } 261 262 /** Close and remove all connections in the pool. */ 263 public void evictAll() { 264 List<Connection> connections; 265 synchronized (this) { 266 connections = new ArrayList<Connection>(this.connections); 267 this.connections.clear(); 268 } 269 270 for (Connection connection : connections) { 271 Util.closeQuietly(connection); 272 } 273 } 274 } 275