Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
217 changes: 118 additions & 99 deletions jdk/src/share/classes/sun/net/www/http/KeepAliveCache.java
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,11 @@

import java.io.IOException;
import java.io.NotSerializableException;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.HashMap;
import java.net.URL;
import java.util.List;

/**
* A class that implements a cache of idle Http connections for keep-alive
Expand Down Expand Up @@ -76,56 +78,68 @@ public KeepAliveCache() {}
* @param url The URL contains info about the host and port
* @param http The HttpClient to be cached
*/
public synchronized void put(final URL url, Object obj, HttpClient http) {
boolean startThread = (keepAliveTimer == null);
if (!startThread) {
if (!keepAliveTimer.isAlive()) {
startThread = true;
public void put(final URL url, Object obj, HttpClient http) {
// this method may need to close an HttpClient, either because
// it is not cacheable, or because the cache is at its capacity.
// In the latter case, we close the least recently used client.
// The client to close is stored in oldClient, and is closed
// after cacheLock is released.
HttpClient oldClient = null;
synchronized(this) {
boolean startThread = (keepAliveTimer == null);
if (!startThread) {
if (!keepAliveTimer.isAlive()) {
startThread = true;
}
}
}
if (startThread) {
clear();
/* Unfortunately, we can't always believe the keep-alive timeout we got
* back from the server. If I'm connected through a Netscape proxy
* to a server that sent me a keep-alive
* time of 15 sec, the proxy unilaterally terminates my connection
* The robustness to get around this is in HttpClient.parseHTTP()
*/
final KeepAliveCache cache = this;
java.security.AccessController.doPrivileged(
new java.security.PrivilegedAction<Void>() {
public Void run() {
// We want to create the Keep-Alive-Timer in the
// system threadgroup
ThreadGroup grp = Thread.currentThread().getThreadGroup();
ThreadGroup parent = null;
while ((parent = grp.getParent()) != null) {
grp = parent;
if (startThread) {
clear();
/* Unfortunately, we can't always believe the keep-alive timeout we got
* back from the server. If I'm connected through a Netscape proxy
* to a server that sent me a keep-alive
* time of 15 sec, the proxy unilaterally terminates my connection
* The robustness to get around this is in HttpClient.parseHTTP()
*/
final KeepAliveCache cache = this;
java.security.AccessController.doPrivileged(
new java.security.PrivilegedAction<Void>() {
public Void run() {
// We want to create the Keep-Alive-Timer in the
// system threadgroup
ThreadGroup grp = Thread.currentThread().getThreadGroup();
ThreadGroup parent = null;
while ((parent = grp.getParent()) != null) {
grp = parent;
}

keepAliveTimer = new Thread(grp, cache, "Keep-Alive-Timer");
keepAliveTimer.setDaemon(true);
keepAliveTimer.setPriority(Thread.MAX_PRIORITY - 2);
// Set the context class loader to null in order to avoid
// keeping a strong reference to an application classloader.
keepAliveTimer.setContextClassLoader(null);
keepAliveTimer.start();
return null;
}
});
}

keepAliveTimer = new Thread(grp, cache, "Keep-Alive-Timer");
keepAliveTimer.setDaemon(true);
keepAliveTimer.setPriority(Thread.MAX_PRIORITY - 2);
// Set the context class loader to null in order to avoid
// keeping a strong reference to an application classloader.
keepAliveTimer.setContextClassLoader(null);
keepAliveTimer.start();
return null;
}
});
KeepAliveKey key = new KeepAliveKey(url, obj);
ClientVector v = super.get(key);

if (v == null) {
int keepAliveTimeout = http.getKeepAliveTimeout();
v = new ClientVector(keepAliveTimeout > 0?
keepAliveTimeout*1000 : LIFETIME);
v.put(http);
super.put(key, v);
} else {
oldClient = v.put(http);
}
}

KeepAliveKey key = new KeepAliveKey(url, obj);
ClientVector v = super.get(key);

if (v == null) {
int keepAliveTimeout = http.getKeepAliveTimeout();
v = new ClientVector(keepAliveTimeout > 0?
keepAliveTimeout*1000 : LIFETIME);
v.put(http);
super.put(key, v);
} else {
v.put(http);
// close after releasing locks
if (oldClient != null) {
oldClient.closeServer();
}
}

Expand All @@ -135,7 +149,7 @@ public synchronized void remove (HttpClient h, Object obj) {
ClientVector v = super.get(key);
if (v != null) {
v.remove(h);
if (v.empty()) {
if (v.isEmpty()) {
removeVector(key);
}
}
Expand Down Expand Up @@ -171,39 +185,31 @@ public void run() {
try {
Thread.sleep(LIFETIME);
} catch (InterruptedException e) {}
synchronized (this) {
/* Remove all unused HttpClients. Starting from the
* bottom of the stack (the least-recently used first).
* REMIND: It'd be nice to not remove *all* connections
* that aren't presently in use. One could have been added
* a second ago that's still perfectly valid, and we're
* needlessly axing it. But it's not clear how to do this
* cleanly, and doing it right may be more trouble than it's
* worth.
*/
List<HttpClient> closeList = null;

// Remove all outdated HttpClients.
synchronized (this) {
long currentTime = System.currentTimeMillis();

ArrayList<KeepAliveKey> keysToRemove
= new ArrayList<KeepAliveKey>();
List<KeepAliveKey> keysToRemove = new ArrayList<>();

for (KeepAliveKey key : keySet()) {
ClientVector v = get(key);
synchronized (v) {
int i;

for (i = 0; i < v.size(); i++) {
KeepAliveEntry e = v.elementAt(i);
KeepAliveEntry e = v.peekLast();
while (e != null) {
if ((currentTime - e.idleStartTime) > v.nap) {
HttpClient h = e.hc;
h.closeServer();
v.pollLast();
if (closeList == null) {
closeList = new ArrayList<>();
}
closeList.add(e.hc);
} else {
break;
}
e = v.peekLast();
}
v.subList(0, i).clear();

if (v.size() == 0) {
if (v.isEmpty()) {
keysToRemove.add(key);
}
}
Expand All @@ -213,9 +219,13 @@ public void run() {
removeVector(key);
}
}
} while (size() > 0);

return;
// close connections outside cacheLock
if (closeList != null) {
for (HttpClient hc : closeList) {
hc.closeServer();
}
}
} while (!isEmpty());
}

/*
Expand All @@ -232,12 +242,10 @@ private void readObject(java.io.ObjectInputStream stream)
}
}

/* FILO order for recycling HttpClients, should run in a thread
* to time them out. If > maxConns are in use, block.
/* LIFO order for reusing HttpClients. Most recent entries at the front.
* If > maxConns are in use, discard oldest.
*/


class ClientVector extends java.util.Stack<KeepAliveEntry> {
class ClientVector extends ArrayDeque<KeepAliveEntry> {
private static final long serialVersionUID = -8680532108106489459L;

// sleep time in milliseconds, before cache clear
Expand All @@ -250,31 +258,42 @@ class ClientVector extends java.util.Stack<KeepAliveEntry> {
}

synchronized HttpClient get() {
if (empty()) {
// check the most recent connection, use if still valid
KeepAliveEntry e = peekFirst();
if (e == null) {
return null;
}

long currentTime = System.currentTimeMillis();
if ((currentTime - e.idleStartTime) > nap) {
return null; // all connections stale - will be cleaned up later
} else {
// Loop until we find a connection that has not timed out
HttpClient hc = null;
long currentTime = System.currentTimeMillis();
do {
KeepAliveEntry e = pop();
if ((currentTime - e.idleStartTime) > nap) {
e.hc.closeServer();
} else {
hc = e.hc;
}
} while ((hc== null) && (!empty()));
return hc;
pollFirst();
return e.hc;
}
}

/* remove an HttpClient */
synchronized boolean remove(HttpClient h) {
for (KeepAliveEntry curr : this) {
if (curr.hc == h) {
return super.remove(curr);
}
}
return false;
}

/* return a still valid, unused HttpClient */
synchronized void put(HttpClient h) {
synchronized HttpClient put(HttpClient h) {
HttpClient staleClient = null;
assert KeepAliveCache.getMaxConnections() > 0;
if (size() >= KeepAliveCache.getMaxConnections()) {
h.closeServer(); // otherwise the connection remains in limbo
} else {
push(new KeepAliveEntry(h, System.currentTimeMillis()));
// remove oldest connection
staleClient = removeLast().hc;
}
addFirst(new KeepAliveEntry(h, System.currentTimeMillis()));
// close after releasing the locks
return staleClient;
}

/*
Expand All @@ -293,10 +312,10 @@ private void readObject(java.io.ObjectInputStream stream)


class KeepAliveKey {
private String protocol = null;
private String host = null;
private int port = 0;
private Object obj = null; // additional key, such as socketfactory
private final String protocol;
private final String host;
private final int port;
private final Object obj; // additional key, such as socketfactory

/**
* Constructor
Expand Down Expand Up @@ -337,8 +356,8 @@ public int hashCode() {
}

class KeepAliveEntry {
HttpClient hc;
long idleStartTime;
final HttpClient hc;
final long idleStartTime;

KeepAliveEntry(HttpClient hc, long idleStartTime) {
this.hc = hc;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -426,6 +426,15 @@ public boolean needsTunneling() {
&& proxy.type() != Proxy.Type.SOCKS);
}

@Override
public void closeServer() {
try {
// SSLSocket.close may block up to timeout. Make sure it's short.
serverSocket.setSoTimeout(1);
} catch (Exception e) {}
super.closeServer();
}

@Override
public void afterConnect() throws IOException, UnknownHostException {
if (!isCachedConnection()) {
Expand Down
Loading