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.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