• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
ConnectionPool(int maxIdleConnections, long keepAliveDurationMs)101   public ConnectionPool(int maxIdleConnections, long keepAliveDurationMs) {
102     this.maxIdleConnections = maxIdleConnections;
103     this.keepAliveDurationNs = keepAliveDurationMs * 1000 * 1000;
104   }
105 
getDefault()106   public static ConnectionPool getDefault() {
107     return systemDefault;
108   }
109 
110   /** Returns total number of connections in the pool. */
getConnectionCount()111   public synchronized int getConnectionCount() {
112     return connections.size();
113   }
114 
115   /** @deprecated Use {@link #getMultiplexedConnectionCount()}. */
116   @Deprecated
getSpdyConnectionCount()117   public synchronized int getSpdyConnectionCount() {
118     return getMultiplexedConnectionCount();
119   }
120 
121   /** Returns total number of multiplexed connections in the pool. */
getMultiplexedConnectionCount()122   public synchronized int getMultiplexedConnectionCount() {
123     int total = 0;
124     for (Connection connection : connections) {
125       if (connection.isFramed()) total++;
126     }
127     return total;
128   }
129 
130   /** Returns total number of http connections in the pool. */
getHttpConnectionCount()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. */
get(Address address)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.isFramed()) {
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.isFramed()) {
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    */
recycle(Connection connection)174   void recycle(Connection connection) {
175     if (connection.isFramed()) {
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 
addConnection(Connection connection)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    */
share(Connection connection)218   void share(Connection connection) {
219     if (!connection.isFramed()) 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. */
evictAll()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 
runCleanupUntilPoolIsEmpty()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
performCleanup()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
replaceCleanupExecutorForTests(Executor cleanupExecutor)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
getConnections()334   synchronized List<Connection> getConnections() {
335     return new ArrayList<>(connections);
336   }
337 }
338