Skip to content

Commit 244d194

Browse files
committed
8293562: KeepAliveCache Blocks Threads while Closing Connections
Backport-of: 03f25a9c6924430ec4063b801b2b6ca55b9067c9
1 parent eb95804 commit 244d194

File tree

3 files changed

+303
-68
lines changed

3 files changed

+303
-68
lines changed

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

+56-68
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,12 @@ public KeepAliveCache() {}
121121
*/
122122
@SuppressWarnings("removal")
123123
public void put(final URL url, Object obj, HttpClient http) {
124+
// this method may need to close an HttpClient, either because
125+
// it is not cacheable, or because the cache is at its capacity.
126+
// In the latter case, we close the least recently used client.
127+
// The client to close is stored in oldClient, and is closed
128+
// after cacheLock is released.
129+
HttpClient oldClient = null;
124130
cacheLock.lock();
125131
try {
126132
boolean startThread = (keepAliveTimer == null);
@@ -171,42 +177,29 @@ public Void run() {
171177
// alive, which could be 0, if the user specified 0 for the property
172178
assert keepAliveTimeout >= 0;
173179
if (keepAliveTimeout == 0) {
174-
http.closeServer();
180+
oldClient = http;
175181
} else {
176182
v = new ClientVector(keepAliveTimeout * 1000);
177183
v.put(http);
178184
super.put(key, v);
179185
}
180186
} else {
181-
v.put(http);
187+
oldClient = v.put(http);
182188
}
183189
} finally {
184190
cacheLock.unlock();
185191
}
192+
// close after releasing locks
193+
if (oldClient != null) {
194+
oldClient.closeServer();
195+
}
186196
}
187197

188198
// returns the keep alive set by user in system property or -1 if not set
189199
private static int getUserKeepAlive(boolean isProxy) {
190200
return isProxy ? userKeepAliveProxy : userKeepAliveServer;
191201
}
192202

193-
/* remove an obsolete HttpClient from its VectorCache */
194-
public void remove(HttpClient h, Object obj) {
195-
cacheLock.lock();
196-
try {
197-
KeepAliveKey key = new KeepAliveKey(h.url, obj);
198-
ClientVector v = super.get(key);
199-
if (v != null) {
200-
v.remove(h);
201-
if (v.isEmpty()) {
202-
removeVector(key);
203-
}
204-
}
205-
} finally {
206-
cacheLock.unlock();
207-
}
208-
}
209-
210203
/* called by a clientVector thread when all its connections have timed out
211204
* and that vector of connections should be removed.
212205
*/
@@ -242,6 +235,7 @@ public void run() {
242235
try {
243236
Thread.sleep(LIFETIME);
244237
} catch (InterruptedException e) {}
238+
List<HttpClient> closeList = null;
245239

246240
// Remove all outdated HttpClients.
247241
cacheLock.lock();
@@ -253,15 +247,18 @@ public void run() {
253247
ClientVector v = get(key);
254248
v.lock();
255249
try {
256-
KeepAliveEntry e = v.peek();
250+
KeepAliveEntry e = v.peekLast();
257251
while (e != null) {
258252
if ((currentTime - e.idleStartTime) > v.nap) {
259-
v.poll();
260-
e.hc.closeServer();
253+
v.pollLast();
254+
if (closeList == null) {
255+
closeList = new ArrayList<>();
256+
}
257+
closeList.add(e.hc);
261258
} else {
262259
break;
263260
}
264-
e = v.peek();
261+
e = v.peekLast();
265262
}
266263

267264
if (v.isEmpty()) {
@@ -277,6 +274,12 @@ public void run() {
277274
}
278275
} finally {
279276
cacheLock.unlock();
277+
// close connections outside cacheLock
278+
if (closeList != null) {
279+
for (HttpClient hc : closeList) {
280+
hc.closeServer();
281+
}
282+
}
280283
}
281284
} while (!isEmpty());
282285
}
@@ -297,8 +300,8 @@ private void readObject(ObjectInputStream stream)
297300
}
298301
}
299302

300-
/* FILO order for recycling HttpClients, should run in a thread
301-
* 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.
302305
*/
303306
class ClientVector extends ArrayDeque<KeepAliveEntry> {
304307
@java.io.Serial
@@ -312,62 +315,47 @@ class ClientVector extends ArrayDeque<KeepAliveEntry> {
312315
this.nap = nap;
313316
}
314317

318+
/* return a still valid, idle HttpClient */
315319
HttpClient get() {
316320
lock();
317321
try {
318-
if (isEmpty()) {
322+
// check the most recent connection, use if still valid
323+
KeepAliveEntry e = peekFirst();
324+
if (e == null) {
319325
return null;
320326
}
321-
322-
// Loop until we find a connection that has not timed out
323-
HttpClient hc = null;
324327
long currentTime = System.currentTimeMillis();
325-
do {
326-
KeepAliveEntry e = pop();
327-
if ((currentTime - e.idleStartTime) > nap) {
328-
e.hc.closeServer();
329-
} else {
330-
hc = e.hc;
331-
if (KeepAliveCache.logger.isLoggable(PlatformLogger.Level.FINEST)) {
332-
String msg = "cached HttpClient was idle for "
333-
+ Long.toString(currentTime - e.idleStartTime);
334-
KeepAliveCache.logger.finest(msg);
335-
}
336-
}
337-
} while ((hc == null) && (!isEmpty()));
338-
return hc;
339-
} finally {
340-
unlock();
341-
}
342-
}
343-
344-
/* return a still valid, unused HttpClient */
345-
void put(HttpClient h) {
346-
lock();
347-
try {
348-
if (size() >= KeepAliveCache.getMaxConnections()) {
349-
h.closeServer(); // otherwise the connection remains in limbo
328+
if ((currentTime - e.idleStartTime) > nap) {
329+
return null; // all connections stale - will be cleaned up later
350330
} else {
351-
push(new KeepAliveEntry(h, System.currentTimeMillis()));
331+
pollFirst();
332+
if (KeepAliveCache.logger.isLoggable(PlatformLogger.Level.FINEST)) {
333+
String msg = "cached HttpClient was idle for "
334+
+ Long.toString(currentTime - e.idleStartTime);
335+
KeepAliveCache.logger.finest(msg);
336+
}
337+
return e.hc;
352338
}
353339
} finally {
354340
unlock();
355341
}
356342
}
357343

358-
/* remove an HttpClient */
359-
boolean remove(HttpClient h) {
344+
HttpClient put(HttpClient h) {
345+
HttpClient staleClient = null;
360346
lock();
361347
try {
362-
for (KeepAliveEntry curr : this) {
363-
if (curr.hc == h) {
364-
return super.remove(curr);
365-
}
348+
assert KeepAliveCache.getMaxConnections() > 0;
349+
if (size() >= KeepAliveCache.getMaxConnections()) {
350+
// remove oldest connection
351+
staleClient = removeLast().hc;
366352
}
367-
return false;
353+
addFirst(new KeepAliveEntry(h, System.currentTimeMillis()));
368354
} finally {
369355
unlock();
370356
}
357+
// close after releasing the locks
358+
return staleClient;
371359
}
372360

373361
final void lock() {
@@ -395,10 +383,10 @@ private void readObject(ObjectInputStream stream)
395383
}
396384

397385
class KeepAliveKey {
398-
private String protocol = null;
399-
private String host = null;
400-
private int port = 0;
401-
private Object obj = null; // additional key, such as socketfactory
386+
private final String protocol;
387+
private final String host;
388+
private final int port;
389+
private final Object obj; // additional key, such as socketfactory
402390

403391
/**
404392
* Constructor
@@ -439,8 +427,8 @@ public int hashCode() {
439427
}
440428

441429
class KeepAliveEntry {
442-
HttpClient hc;
443-
long idleStartTime;
430+
final HttpClient hc;
431+
final long idleStartTime;
444432

445433
KeepAliveEntry(HttpClient hc, long idleStartTime) {
446434
this.hc = hc;

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

+9
Original file line numberDiff line numberDiff line change
@@ -436,6 +436,15 @@ protected Socket createSocket() throws IOException {
436436
}
437437
}
438438

439+
@Override
440+
public void closeServer() {
441+
try {
442+
// SSLSocket.close may block up to timeout. Make sure it's short.
443+
serverSocket.setSoTimeout(1);
444+
} catch (Exception e) {}
445+
super.closeServer();
446+
}
447+
439448

440449
@Override
441450
public boolean needsTunneling() {

0 commit comments

Comments
 (0)