Skip to content

Commit 770c1f6

Browse files
Pooja-DPjerboaa
authored andcommitted
8293562: KeepAliveCache Blocks Threads while Closing Connections
Reviewed-by: sgehwolf Backport-of: 03f25a9c6924430ec4063b801b2b6ca55b9067c9
1 parent ccbb928 commit 770c1f6

File tree

3 files changed

+332
-63
lines changed

3 files changed

+332
-63
lines changed

src/java.base/share/classes/sun/net/www/http/KeepAliveCache.java

+86-63
Original file line numberDiff line numberDiff line change
@@ -113,38 +113,45 @@ public KeepAliveCache() {}
113113
* @param url The URL contains info about the host and port
114114
* @param http The HttpClient to be cached
115115
*/
116-
public synchronized void put(final URL url, Object obj, HttpClient http) {
117-
boolean startThread = (keepAliveTimer == null);
118-
if (!startThread) {
119-
if (!keepAliveTimer.isAlive()) {
120-
startThread = true;
121-
}
122-
}
123-
if (startThread) {
124-
clear();
125-
/* Unfortunately, we can't always believe the keep-alive timeout we got
126-
* back from the server. If I'm connected through a Netscape proxy
127-
* to a server that sent me a keep-alive
128-
* time of 15 sec, the proxy unilaterally terminates my connection
129-
* The robustness to get around this is in HttpClient.parseHTTP()
130-
*/
131-
final KeepAliveCache cache = this;
132-
AccessController.doPrivileged(new PrivilegedAction<>() {
133-
public Void run() {
134-
keepAliveTimer = InnocuousThread.newSystemThread("Keep-Alive-Timer", cache);
135-
keepAliveTimer.setDaemon(true);
136-
keepAliveTimer.setPriority(Thread.MAX_PRIORITY - 2);
137-
keepAliveTimer.start();
138-
return null;
116+
public void put(final URL url, Object obj, HttpClient http) {
117+
// this method may need to close an HttpClient, either because
118+
// it is not cacheable, or because the cache is at its capacity.
119+
// In the latter case, we close the least recently used client.
120+
// The client to close is stored in oldClient, and is closed
121+
// after cacheLock is released.
122+
HttpClient oldClient = null;
123+
synchronized (this) {
124+
boolean startThread = (keepAliveTimer == null);
125+
if (!startThread) {
126+
if (!keepAliveTimer.isAlive()) {
127+
startThread = true;
139128
}
140-
});
141-
}
129+
}
130+
if (startThread) {
131+
clear();
132+
/* Unfortunately, we can't always believe the keep-alive timeout we got
133+
* back from the server. If I'm connected through a Netscape proxy
134+
* to a server that sent me a keep-alive
135+
* time of 15 sec, the proxy unilaterally terminates my connection
136+
* The robustness to get around this is in HttpClient.parseHTTP()
137+
*/
138+
final KeepAliveCache cache = this;
139+
AccessController.doPrivileged(new PrivilegedAction<>() {
140+
public Void run() {
141+
keepAliveTimer = InnocuousThread.newSystemThread("Keep-Alive-Timer", cache);
142+
keepAliveTimer.setDaemon(true);
143+
keepAliveTimer.setPriority(Thread.MAX_PRIORITY - 2);
144+
keepAliveTimer.start();
145+
return null;
146+
}
147+
});
148+
}
142149

143-
KeepAliveKey key = new KeepAliveKey(url, obj);
144-
ClientVector v = super.get(key);
150+
KeepAliveKey key = new KeepAliveKey(url, obj);
151+
ClientVector v = super.get(key);
145152

146-
if (v == null) {
147-
int keepAliveTimeout = http.getKeepAliveTimeout();
153+
if (v == null) {
154+
int keepAliveTimeout = http.getKeepAliveTimeout();
148155
if (keepAliveTimeout == 0) {
149156
keepAliveTimeout = getUserKeepAlive(http.getUsingProxy());
150157
if (keepAliveTimeout == -1) {
@@ -164,14 +171,19 @@ public Void run() {
164171
// alive, which could be 0, if the user specified 0 for the property
165172
assert keepAliveTimeout >= 0;
166173
if (keepAliveTimeout == 0) {
167-
http.closeServer();
174+
oldClient = http;
168175
} else {
169176
v = new ClientVector(keepAliveTimeout * 1000);
170177
v.put(http);
171178
super.put(key, v);
172179
}
173-
} else {
174-
v.put(http);
180+
} else {
181+
oldClient = v.put(http);
182+
}
183+
}
184+
// close after releasing locks
185+
if (oldClient != null) {
186+
oldClient.closeServer();
175187
}
176188
}
177189

@@ -221,6 +233,7 @@ public void run() {
221233
try {
222234
Thread.sleep(LIFETIME);
223235
} catch (InterruptedException e) {}
236+
List<HttpClient> closeList = null;
224237

225238
// Remove all outdated HttpClients.
226239
synchronized (this) {
@@ -230,15 +243,18 @@ public void run() {
230243
for (KeepAliveKey key : keySet()) {
231244
ClientVector v = get(key);
232245
synchronized (v) {
233-
KeepAliveEntry e = v.peek();
246+
KeepAliveEntry e = v.peekLast();
234247
while (e != null) {
235248
if ((currentTime - e.idleStartTime) > v.nap) {
236-
v.poll();
237-
e.hc.closeServer();
249+
v.pollLast();
250+
if (closeList == null) {
251+
closeList = new ArrayList<>();
252+
}
253+
closeList.add(e.hc);
238254
} else {
239255
break;
240256
}
241-
e = v.peek();
257+
e = v.peekLast();
242258
}
243259

244260
if (v.isEmpty()) {
@@ -251,6 +267,12 @@ public void run() {
251267
removeVector(key);
252268
}
253269
}
270+
// close connections outside cacheLock
271+
if (closeList != null) {
272+
for (HttpClient hc : closeList) {
273+
hc.closeServer();
274+
}
275+
}
254276
} while (!isEmpty());
255277
}
256278

@@ -268,8 +290,8 @@ private void readObject(ObjectInputStream stream)
268290
}
269291
}
270292

271-
/* FILO order for recycling HttpClients, should run in a thread
272-
* to time them out. If > maxConns are in use, block.
293+
/* LIFO order for reusing HttpClients. Most recent entries at the front.
294+
* If > maxConns are in use, discard oldest.
273295
*/
274296
class ClientVector extends ArrayDeque<KeepAliveEntry> {
275297
private static final long serialVersionUID = -8680532108106489459L;
@@ -282,36 +304,37 @@ class ClientVector extends ArrayDeque<KeepAliveEntry> {
282304
}
283305

284306
synchronized HttpClient get() {
285-
if (isEmpty()) {
307+
// check the most recent connection, use if still valid
308+
KeepAliveEntry e = peekFirst();
309+
if (e == null) {
286310
return null;
287311
}
288312

289-
// Loop until we find a connection that has not timed out
290-
HttpClient hc = null;
291313
long currentTime = System.currentTimeMillis();
292-
do {
293-
KeepAliveEntry e = pop();
294-
if ((currentTime - e.idleStartTime) > nap) {
295-
e.hc.closeServer();
296-
} else {
297-
hc = e.hc;
298-
if (KeepAliveCache.logger.isLoggable(PlatformLogger.Level.FINEST)) {
299-
String msg = "cached HttpClient was idle for "
314+
if ((currentTime - e.idleStartTime) > nap) {
315+
return null; // all connections stale - will be cleaned up later
316+
} else {
317+
pollFirst();
318+
if (KeepAliveCache.logger.isLoggable(PlatformLogger.Level.FINEST)) {
319+
String msg = "cached HttpClient was idle for "
300320
+ Long.toString(currentTime - e.idleStartTime);
301-
KeepAliveCache.logger.finest(msg);
302-
}
321+
KeepAliveCache.logger.finest(msg);
303322
}
304-
} while ((hc == null) && (!isEmpty()));
305-
return hc;
323+
return e.hc;
324+
}
306325
}
307326

308327
/* return a still valid, unused HttpClient */
309-
synchronized void put(HttpClient h) {
328+
synchronized HttpClient put(HttpClient h) {
329+
HttpClient staleClient = null;
330+
assert KeepAliveCache.getMaxConnections() > 0;
310331
if (size() >= KeepAliveCache.getMaxConnections()) {
311-
h.closeServer(); // otherwise the connection remains in limbo
312-
} else {
313-
push(new KeepAliveEntry(h, System.currentTimeMillis()));
332+
// remove oldest connection
333+
staleClient = removeLast().hc;
314334
}
335+
addFirst(new KeepAliveEntry(h, System.currentTimeMillis()));
336+
// close after releasing the locks
337+
return staleClient;
315338
}
316339

317340
/* remove an HttpClient */
@@ -339,10 +362,10 @@ private void readObject(ObjectInputStream stream)
339362
}
340363

341364
class KeepAliveKey {
342-
private String protocol = null;
343-
private String host = null;
344-
private int port = 0;
345-
private Object obj = null; // additional key, such as socketfactory
365+
private final String protocol;
366+
private final String host;
367+
private final int port;
368+
private final Object obj; // additional key, such as socketfactory
346369

347370
/**
348371
* Constructor
@@ -383,8 +406,8 @@ public int hashCode() {
383406
}
384407

385408
class KeepAliveEntry {
386-
HttpClient hc;
387-
long idleStartTime;
409+
final HttpClient hc;
410+
final long idleStartTime;
388411

389412
KeepAliveEntry(HttpClient hc, long idleStartTime) {
390413
this.hc = hc;

src/java.base/share/classes/sun/net/www/protocol/https/HttpsClient.java

+8
Original file line numberDiff line numberDiff line change
@@ -428,6 +428,14 @@ protected Socket createSocket() throws IOException {
428428
}
429429
}
430430

431+
@Override
432+
public void closeServer() {
433+
try {
434+
// SSLSocket.close may block up to timeout. Make sure it's short.
435+
serverSocket.setSoTimeout(1);
436+
} catch (Exception e) {}
437+
super.closeServer();
438+
}
431439

432440
@Override
433441
public boolean needsTunneling() {

0 commit comments

Comments
 (0)