| /* |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. |
| * The ASF licenses this file to You under the Apache License, Version 2.0 |
| * (the "License"); you may not use this file except in compliance with |
| * the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| package com.squareup.okhttp; |
| |
| import com.squareup.okhttp.internal.Internal; |
| import com.squareup.okhttp.internal.RouteDatabase; |
| import com.squareup.okhttp.internal.Util; |
| import com.squareup.okhttp.internal.http.StreamAllocation; |
| import com.squareup.okhttp.internal.io.RealConnection; |
| import java.lang.ref.Reference; |
| import java.util.ArrayDeque; |
| import java.util.ArrayList; |
| import java.util.Deque; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.concurrent.Executor; |
| import java.util.concurrent.LinkedBlockingQueue; |
| import java.util.concurrent.ThreadPoolExecutor; |
| import java.util.concurrent.TimeUnit; |
| |
| /** |
| * Manages reuse of HTTP and SPDY connections for reduced network latency. HTTP |
| * requests that share the same {@link com.squareup.okhttp.Address} may share a |
| * {@link Connection}. This class implements the policy of which connections to |
| * keep open for future use. |
| * |
| * <p>The {@link #getDefault() system-wide default} uses system properties for |
| * tuning parameters: |
| * <ul> |
| * <li>{@code http.keepAlive} true if HTTP and SPDY connections should be |
| * pooled at all. Default is true. |
| * <li>{@code http.maxConnections} maximum number of idle connections to |
| * each to keep in the pool. Default is 5. |
| * <li>{@code http.keepAliveDuration} Time in milliseconds to keep the |
| * connection alive in the pool before closing it. Default is 5 minutes. |
| * This property isn't used by {@code HttpURLConnection}. |
| * </ul> |
| * |
| * <p>The default instance <i>doesn't</i> adjust its configuration as system |
| * properties are changed. This assumes that the applications that set these |
| * parameters do so before making HTTP connections, and that this class is |
| * initialized lazily. |
| */ |
| public final class ConnectionPool { |
| private static final long DEFAULT_KEEP_ALIVE_DURATION_MS = 5 * 60 * 1000; // 5 min |
| |
| private static final ConnectionPool systemDefault; |
| |
| static { |
| String keepAlive = System.getProperty("http.keepAlive"); |
| String keepAliveDuration = System.getProperty("http.keepAliveDuration"); |
| String maxIdleConnections = System.getProperty("http.maxConnections"); |
| long keepAliveDurationMs = keepAliveDuration != null |
| ? Long.parseLong(keepAliveDuration) |
| : DEFAULT_KEEP_ALIVE_DURATION_MS; |
| if (keepAlive != null && !Boolean.parseBoolean(keepAlive)) { |
| systemDefault = new ConnectionPool(0, keepAliveDurationMs); |
| } else if (maxIdleConnections != null) { |
| systemDefault = new ConnectionPool(Integer.parseInt(maxIdleConnections), keepAliveDurationMs); |
| } else { |
| systemDefault = new ConnectionPool(5, keepAliveDurationMs); |
| } |
| } |
| |
| /** |
| * A background thread is used to cleanup expired connections. There will be, at most, a single |
| * thread running per connection pool. We use a thread pool executor because it can shrink to |
| * zero threads, permitting this pool to be garbage collected. |
| */ |
| private final Executor executor = new ThreadPoolExecutor( |
| 0 /* corePoolSize */, 1 /* maximumPoolSize */, 60L /* keepAliveTime */, TimeUnit.SECONDS, |
| new LinkedBlockingQueue<Runnable>(), Util.threadFactory("OkHttp ConnectionPool", true)); |
| |
| /** The maximum number of idle connections for each address. */ |
| private final int maxIdleConnections; |
| private final long keepAliveDurationNs; |
| private Runnable cleanupRunnable = new Runnable() { |
| @Override public void run() { |
| while (true) { |
| long waitNanos = cleanup(System.nanoTime()); |
| if (waitNanos == -1) return; |
| if (waitNanos > 0) { |
| long waitMillis = waitNanos / 1000000L; |
| waitNanos -= (waitMillis * 1000000L); |
| synchronized (ConnectionPool.this) { |
| try { |
| ConnectionPool.this.wait(waitMillis, (int) waitNanos); |
| } catch (InterruptedException ignored) { |
| } |
| } |
| } |
| } |
| } |
| }; |
| |
| private final Deque<RealConnection> connections = new ArrayDeque<>(); |
| final RouteDatabase routeDatabase = new RouteDatabase(); |
| |
| public ConnectionPool(int maxIdleConnections, long keepAliveDurationMs) { |
| this(maxIdleConnections, keepAliveDurationMs, TimeUnit.MILLISECONDS); |
| } |
| |
| public ConnectionPool(int maxIdleConnections, long keepAliveDuration, TimeUnit timeUnit) { |
| this.maxIdleConnections = maxIdleConnections; |
| this.keepAliveDurationNs = timeUnit.toNanos(keepAliveDuration); |
| |
| // Put a floor on the keep alive duration, otherwise cleanup will spin loop. |
| if (keepAliveDuration <= 0) { |
| throw new IllegalArgumentException("keepAliveDuration <= 0: " + keepAliveDuration); |
| } |
| } |
| |
| public static ConnectionPool getDefault() { |
| return systemDefault; |
| } |
| |
| /** Returns the number of idle connections in the pool. */ |
| public synchronized int getIdleConnectionCount() { |
| int total = 0; |
| for (RealConnection connection : connections) { |
| if (connection.allocations.isEmpty()) total++; |
| } |
| return total; |
| } |
| |
| /** |
| * Returns total number of connections in the pool. Note that prior to OkHttp 2.7 this included |
| * only idle connections and SPDY connections. In OkHttp 2.7 this includes all connections, both |
| * active and inactive. Use {@link #getIdleConnectionCount()} to count connections not currently |
| * in use. |
| */ |
| public synchronized int getConnectionCount() { |
| return connections.size(); |
| } |
| |
| /** @deprecated Use {@link #getMultiplexedConnectionCount()}. */ |
| @Deprecated |
| public synchronized int getSpdyConnectionCount() { |
| return getMultiplexedConnectionCount(); |
| } |
| |
| /** Returns total number of multiplexed connections in the pool. */ |
| public synchronized int getMultiplexedConnectionCount() { |
| int total = 0; |
| for (RealConnection connection : connections) { |
| if (connection.isMultiplexed()) total++; |
| } |
| return total; |
| } |
| |
| /** Returns total number of http connections in the pool. */ |
| public synchronized int getHttpConnectionCount() { |
| return connections.size() - getMultiplexedConnectionCount(); |
| } |
| |
| /** Returns a recycled connection to {@code address}, or null if no such connection exists. */ |
| RealConnection get(Address address, StreamAllocation streamAllocation) { |
| assert (Thread.holdsLock(this)); |
| for (RealConnection connection : connections) { |
| // TODO(jwilson): this is awkward. We're already holding a lock on 'this', and |
| // connection.allocationLimit() may also lock the FramedConnection. |
| if (connection.allocations.size() < connection.allocationLimit() |
| && address.equals(connection.getRoute().address) |
| && !connection.noNewStreams) { |
| streamAllocation.acquire(connection); |
| return connection; |
| } |
| } |
| return null; |
| } |
| |
| void put(RealConnection connection) { |
| assert (Thread.holdsLock(this)); |
| if (connections.isEmpty()) { |
| executor.execute(cleanupRunnable); |
| } |
| connections.add(connection); |
| } |
| |
| /** |
| * Notify this pool that {@code connection} has become idle. Returns true if the connection |
| * has been removed from the pool and should be closed. |
| */ |
| boolean connectionBecameIdle(RealConnection connection) { |
| assert (Thread.holdsLock(this)); |
| if (connection.noNewStreams || maxIdleConnections == 0) { |
| connections.remove(connection); |
| return true; |
| } else { |
| notifyAll(); // Awake the cleanup thread: we may have exceeded the idle connection limit. |
| return false; |
| } |
| } |
| |
| /** Close and remove all idle connections in the pool. */ |
| public void evictAll() { |
| List<RealConnection> evictedConnections = new ArrayList<>(); |
| synchronized (this) { |
| for (Iterator<RealConnection> i = connections.iterator(); i.hasNext(); ) { |
| RealConnection connection = i.next(); |
| if (connection.allocations.isEmpty()) { |
| connection.noNewStreams = true; |
| evictedConnections.add(connection); |
| i.remove(); |
| } |
| } |
| } |
| |
| for (RealConnection connection : evictedConnections) { |
| Util.closeQuietly(connection.getSocket()); |
| } |
| } |
| |
| /** |
| * Performs maintenance on this pool, evicting the connection that has been idle the longest if |
| * either it has exceeded the keep alive limit or the idle connections limit. |
| * |
| * <p>Returns the duration in nanos to sleep until the next scheduled call to this method. |
| * Returns -1 if no further cleanups are required. |
| */ |
| long cleanup(long now) { |
| int inUseConnectionCount = 0; |
| int idleConnectionCount = 0; |
| RealConnection longestIdleConnection = null; |
| long longestIdleDurationNs = Long.MIN_VALUE; |
| |
| // Find either a connection to evict, or the time that the next eviction is due. |
| synchronized (this) { |
| for (Iterator<RealConnection> i = connections.iterator(); i.hasNext(); ) { |
| RealConnection connection = i.next(); |
| |
| // If the connection is in use, keep searching. |
| if (pruneAndGetAllocationCount(connection, now) > 0) { |
| inUseConnectionCount++; |
| continue; |
| } |
| |
| idleConnectionCount++; |
| |
| // If the connection is ready to be evicted, we're done. |
| long idleDurationNs = now - connection.idleAtNanos; |
| if (idleDurationNs > longestIdleDurationNs) { |
| longestIdleDurationNs = idleDurationNs; |
| longestIdleConnection = connection; |
| } |
| } |
| |
| if (longestIdleDurationNs >= this.keepAliveDurationNs |
| || idleConnectionCount > this.maxIdleConnections) { |
| // We've found a connection to evict. Remove it from the list, then close it below (outside |
| // of the synchronized block). |
| connections.remove(longestIdleConnection); |
| |
| } else if (idleConnectionCount > 0) { |
| // A connection will be ready to evict soon. |
| return keepAliveDurationNs - longestIdleDurationNs; |
| |
| } else if (inUseConnectionCount > 0) { |
| // All connections are in use. It'll be at least the keep alive duration 'til we run again. |
| return keepAliveDurationNs; |
| |
| } else { |
| // No connections, idle or in use. |
| return -1; |
| } |
| } |
| |
| Util.closeQuietly(longestIdleConnection.getSocket()); |
| |
| // Cleanup again immediately. |
| return 0; |
| } |
| |
| /** |
| * Prunes any leaked allocations and then returns the number of remaining live allocations on |
| * {@code connection}. Allocations are leaked if the connection is tracking them but the |
| * application code has abandoned them. Leak detection is imprecise and relies on garbage |
| * collection. |
| */ |
| private int pruneAndGetAllocationCount(RealConnection connection, long now) { |
| List<Reference<StreamAllocation>> references = connection.allocations; |
| for (int i = 0; i < references.size(); ) { |
| Reference<StreamAllocation> reference = references.get(i); |
| |
| if (reference.get() != null) { |
| i++; |
| continue; |
| } |
| |
| // Android-removed: Drop warning about a leak that may not be the app's fault. |
| // We can't tell here whether the app accessed the response body (InputStream) or |
| // only the header fields; at least in the latter case, the app has done nothing |
| // wrong so we shouldn't warn. http://b/64789755 |
| // // We've discovered a leaked allocation. This is an application bug. |
| // Internal.logger.warning("A connection to " + connection.getRoute().getAddress().url() |
| // + " was leaked. Did you forget to close a response body?"); |
| references.remove(i); |
| connection.noNewStreams = true; |
| |
| // If this was the last allocation, the connection is eligible for immediate eviction. |
| if (references.isEmpty()) { |
| connection.idleAtNanos = now - keepAliveDurationNs; |
| return 0; |
| } |
| } |
| |
| return references.size(); |
| } |
| |
| void setCleanupRunnableForTest(Runnable cleanupRunnable) { |
| this.cleanupRunnable = cleanupRunnable; |
| } |
| } |