/* * 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.Platform; import com.squareup.okhttp.internal.Util; import java.net.SocketException; import java.util.ArrayList; import java.util.LinkedList; import java.util.List; import java.util.ListIterator; 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 com.squareup.okhttp.Connection}. This class implements the policy of * which connections to keep open for future use. * *
The {@link #getDefault() system-wide default} uses system properties for * tuning parameters: *
The default instance doesn't 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);
}
}
/** The maximum number of idle connections for each address. */
private final int maxIdleConnections;
private final long keepAliveDurationNs;
private final LinkedList A {@link ThreadPoolExecutor} is used and not a
* {@link java.util.concurrent.ScheduledThreadPoolExecutor}; ScheduledThreadPoolExecutors do not
* shrink. This executor shrinks the thread pool after a period of inactivity, and starts threads
* as needed. Delays are instead handled by the {@link #connectionsCleanupRunnable}. It is
* important that the {@link #connectionsCleanupRunnable} stops eventually, otherwise it will pin
* the thread, and thus the connection pool, in memory.
*/
private Executor executor = new ThreadPoolExecutor(
0 /* corePoolSize */, 1 /* maximumPoolSize */, 60L /* keepAliveTime */, TimeUnit.SECONDS,
new LinkedBlockingQueue It is an error to use {@code connection} after calling this method.
*/
void recycle(Connection connection) {
if (connection.isSpdy()) {
return;
}
if (!connection.clearOwner()) {
return; // This connection isn't eligible for reuse.
}
if (!connection.isAlive()) {
Util.closeQuietly(connection.getSocket());
return;
}
try {
Platform.get().untagSocket(connection.getSocket());
} catch (SocketException e) {
// When unable to remove tagging, skip recycling and close.
Platform.get().logW("Unable to untagSocket(): " + e);
Util.closeQuietly(connection.getSocket());
return;
}
synchronized (this) {
addConnection(connection);
connection.incrementRecycleCount();
connection.resetIdleStartTime();
}
}
private void addConnection(Connection connection) {
boolean empty = connections.isEmpty();
connections.addFirst(connection);
if (empty) {
executor.execute(connectionsCleanupRunnable);
} else {
notifyAll();
}
}
/**
* Shares the SPDY connection with the pool. Callers to this method may
* continue to use {@code connection}.
*/
void share(Connection connection) {
if (!connection.isSpdy()) throw new IllegalArgumentException();
if (!connection.isAlive()) return;
synchronized (this) {
addConnection(connection);
}
}
/** Close and remove all connections in the pool. */
public void evictAll() {
ListThe pool is empty.
* In this case, this method returns false and the eviction job should exit because there are no
* further cleanup tasks coming. (If additional connections are added to the pool, another cleanup
* job must be enqueued.)
*
* Connections were evicted.
* At least one connections was eligible for immediate eviction and was evicted. The method
* returns true and cleanup should continue.
*
* We waited to evict.
* None of the pooled connections were eligible for immediate eviction. Instead, we waited until
* either a connection became eligible for eviction, or the connections list changed. In either
* case, the method returns true and cleanup should continue.
*/
// VisibleForTesting
boolean performCleanup() {
List