Home | History | Annotate | Download | only in okhttp
      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