334 lines
13 KiB
Java
334 lines
13 KiB
Java
/* GENERATED SOURCE. DO NOT MODIFY. */
|
|
/*
|
|
* 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.android.okhttp;
|
|
|
|
import com.android.okhttp.internal.Internal;
|
|
import com.android.okhttp.internal.RouteDatabase;
|
|
import com.android.okhttp.internal.Util;
|
|
import com.android.okhttp.internal.http.StreamAllocation;
|
|
import com.android.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.android.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.
|
|
* @hide This class is not part of the Android public SDK API
|
|
*/
|
|
public final class ConnectionPool {
|
|
private static final long DEFAULT_KEEP_ALIVE_DURATION_MS = 5 * 60 * 1000; // 5 min
|
|
|
|
@android.compat.annotation.UnsupportedAppUsage
|
|
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. */
|
|
@android.compat.annotation.UnsupportedAppUsage
|
|
private final int maxIdleConnections;
|
|
@android.compat.annotation.UnsupportedAppUsage
|
|
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) {
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
@android.compat.annotation.UnsupportedAppUsage
|
|
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;
|
|
}
|
|
}
|