Skip to content

Commit 853a87a

Browse files
Dhamoder Nallagnu-andrew
Dhamoder Nalla
authored andcommittedMay 14, 2024
8293562: KeepAliveCache Blocks Threads while Closing Connections
Reviewed-by: sgehwolf, phh, andrew Backport-of: 770c1f65c588f3156f9b70097df752d8059c1038
1 parent 89def4d commit 853a87a

File tree

3 files changed

+342
-74
lines changed

3 files changed

+342
-74
lines changed
 

‎jdk/src/share/classes/sun/net/www/http/KeepAliveCache.java

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

141-
keepAliveTimer = new Thread(grp, cache, "Keep-Alive-Timer");
142-
keepAliveTimer.setDaemon(true);
143-
keepAliveTimer.setPriority(Thread.MAX_PRIORITY - 2);
144-
// Set the context class loader to null in order to avoid
145-
// keeping a strong reference to an application classloader.
146-
keepAliveTimer.setContextClassLoader(null);
147-
keepAliveTimer.start();
148-
return null;
149-
}
150-
});
151-
}
148+
keepAliveTimer = new Thread(grp, cache, "Keep-Alive-Timer");
149+
keepAliveTimer.setDaemon(true);
150+
keepAliveTimer.setPriority(Thread.MAX_PRIORITY - 2);
151+
// Set the context class loader to null in order to avoid
152+
// keeping a strong reference to an application classloader.
153+
keepAliveTimer.setContextClassLoader(null);
154+
keepAliveTimer.start();
155+
return null;
156+
}
157+
});
158+
}
152159

153-
KeepAliveKey key = new KeepAliveKey(url, obj);
154-
ClientVector v = super.get(key);
160+
KeepAliveKey key = new KeepAliveKey(url, obj);
161+
ClientVector v = super.get(key);
155162

156-
if (v == null) {
157-
int keepAliveTimeout = http.getKeepAliveTimeout();
163+
if (v == null) {
164+
int keepAliveTimeout = http.getKeepAliveTimeout();
158165
if (keepAliveTimeout == 0) {
159166
keepAliveTimeout = getUserKeepAlive(http.getUsingProxy());
160167
if (keepAliveTimeout == -1) {
@@ -174,14 +181,19 @@ public Void run() {
174181
// alive, which could be 0, if the user specified 0 for the property
175182
assert keepAliveTimeout >= 0;
176183
if (keepAliveTimeout == 0) {
177-
http.closeServer();
184+
oldClient = http;
178185
} else {
179186
v = new ClientVector(keepAliveTimeout * 1000);
180187
v.put(http);
181188
super.put(key, v);
182189
}
183-
} else {
184-
v.put(http);
190+
} else {
191+
oldClient = v.put(http);
192+
}
193+
}
194+
// close after releasing locks
195+
if (oldClient != null) {
196+
oldClient.closeServer();
185197
}
186198
}
187199

@@ -213,7 +225,6 @@ synchronized void removeVector(KeepAliveKey k) {
213225
* Check to see if this URL has a cached HttpClient
214226
*/
215227
public synchronized HttpClient get(URL url, Object obj) {
216-
217228
KeepAliveKey key = new KeepAliveKey(url, obj);
218229
ClientVector v = super.get(key);
219230
if (v == null) { // nothing in cache yet
@@ -232,6 +243,7 @@ public void run() {
232243
try {
233244
Thread.sleep(LIFETIME);
234245
} catch (InterruptedException e) {}
246+
List<HttpClient> closeList = null;
235247

236248
// Remove all outdated HttpClients.
237249
synchronized (this) {
@@ -241,15 +253,18 @@ public void run() {
241253
for (KeepAliveKey key : keySet()) {
242254
ClientVector v = get(key);
243255
synchronized (v) {
244-
KeepAliveEntry e = v.peek();
256+
KeepAliveEntry e = v.peekLast();
245257
while (e != null) {
246258
if ((currentTime - e.idleStartTime) > v.nap) {
247-
v.poll();
248-
e.hc.closeServer();
259+
v.pollLast();
260+
if (closeList == null) {
261+
closeList = new ArrayList<>();
262+
}
263+
closeList.add(e.hc);
249264
} else {
250265
break;
251266
}
252-
e = v.peek();
267+
e = v.peekLast();
253268
}
254269

255270
if (v.isEmpty()) {
@@ -262,6 +277,12 @@ public void run() {
262277
removeVector(key);
263278
}
264279
}
280+
// close connections outside cacheLock
281+
if (closeList != null) {
282+
for (HttpClient hc : closeList) {
283+
hc.closeServer();
284+
}
285+
}
265286
} while (!isEmpty());
266287
}
267288

@@ -279,8 +300,8 @@ private void readObject(ObjectInputStream stream)
279300
}
280301
}
281302

282-
/* FILO order for recycling HttpClients, should run in a thread
283-
* to time them out. If > maxConns are in use, block.
303+
/* LIFO order for reusing HttpClients. Most recent entries at the front.
304+
* If > maxConns are in use, discard oldest.
284305
*/
285306
class ClientVector extends ArrayDeque<KeepAliveEntry> {
286307
private static final long serialVersionUID = -8680532108106489459L;
@@ -293,36 +314,37 @@ class ClientVector extends ArrayDeque<KeepAliveEntry> {
293314
}
294315

295316
synchronized HttpClient get() {
296-
if (isEmpty()) {
317+
// check the most recent connection, use if still valid
318+
KeepAliveEntry e = peekFirst();
319+
if (e == null) {
297320
return null;
298321
}
299322

300-
// Loop until we find a connection that has not timed out
301-
HttpClient hc = null;
302323
long currentTime = System.currentTimeMillis();
303-
do {
304-
KeepAliveEntry e = pop();
305-
if ((currentTime - e.idleStartTime) > nap) {
306-
e.hc.closeServer();
307-
} else {
308-
hc = e.hc;
309-
if (KeepAliveCache.logger.isLoggable(PlatformLogger.Level.FINEST)) {
310-
String msg = "cached HttpClient was idle for "
324+
if ((currentTime - e.idleStartTime) > nap) {
325+
return null; // all connections stale - will be cleaned up later
326+
} else {
327+
pollFirst();
328+
if (KeepAliveCache.logger.isLoggable(PlatformLogger.Level.FINEST)) {
329+
String msg = "cached HttpClient was idle for "
311330
+ Long.toString(currentTime - e.idleStartTime);
312-
KeepAliveCache.logger.finest(msg);
313-
}
331+
KeepAliveCache.logger.finest(msg);
314332
}
315-
} while ((hc == null) && (!isEmpty()));
316-
return hc;
333+
return e.hc;
334+
}
317335
}
318336

319337
/* return a still valid, unused HttpClient */
320-
synchronized void put(HttpClient h) {
338+
synchronized HttpClient put(HttpClient h) {
339+
HttpClient staleClient = null;
340+
assert KeepAliveCache.getMaxConnections() > 0;
321341
if (size() >= KeepAliveCache.getMaxConnections()) {
322-
h.closeServer(); // otherwise the connection remains in limbo
323-
} else {
324-
push(new KeepAliveEntry(h, System.currentTimeMillis()));
342+
// remove oldest connection
343+
staleClient = removeLast().hc;
325344
}
345+
addFirst(new KeepAliveEntry(h, System.currentTimeMillis()));
346+
// close after releasing the locks
347+
return staleClient;
326348
}
327349

328350
/* remove an HttpClient */
@@ -350,10 +372,10 @@ private void readObject(ObjectInputStream stream)
350372
}
351373

352374
class KeepAliveKey {
353-
private String protocol = null;
354-
private String host = null;
355-
private int port = 0;
356-
private Object obj = null; // additional key, such as socketfactory
375+
private final String protocol;
376+
private final String host;
377+
private final int port;
378+
private final Object obj; // additional key, such as socketfactory
357379

358380
/**
359381
* Constructor
@@ -394,8 +416,8 @@ public int hashCode() {
394416
}
395417

396418
class KeepAliveEntry {
397-
HttpClient hc;
398-
long idleStartTime;
419+
final HttpClient hc;
420+
final long idleStartTime;
399421

400422
KeepAliveEntry(HttpClient hc, long idleStartTime) {
401423
this.hc = hc;

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

+8
Original file line numberDiff line numberDiff line change
@@ -419,6 +419,14 @@ protected Socket createSocket() throws IOException {
419419
}
420420
}
421421

422+
@Override
423+
public void closeServer() {
424+
try {
425+
// SSLSocket.close may block up to timeout. Make sure it's short.
426+
serverSocket.setSoTimeout(1);
427+
} catch (Exception e) {}
428+
super.closeServer();
429+
}
422430

423431
@Override
424432
public boolean needsTunneling() {

1 commit comments

Comments
 (1)

openjdk-notifier[bot] commented on May 14, 2024

@openjdk-notifier[bot]
Please sign in to comment.