Skip to content

Commit 03f25a9

Browse files
committed
8293562: blocked threads with KeepAliveCache.get
Reviewed-by: dfuchs, michaelm
1 parent a69ee85 commit 03f25a9

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
@@ -122,6 +122,12 @@ public KeepAliveCache() {}
122122
*/
123123
@SuppressWarnings("removal")
124124
public void put(final URL url, Object obj, HttpClient http) {
125+
// this method may need to close an HttpClient, either because
126+
// it is not cacheable, or because the cache is at its capacity.
127+
// In the latter case, we close the least recently used client.
128+
// The client to close is stored in oldClient, and is closed
129+
// after cacheLock is released.
130+
HttpClient oldClient = null;
125131
cacheLock.lock();
126132
try {
127133
boolean startThread = (keepAliveTimer == null);
@@ -172,42 +178,29 @@ public Void run() {
172178
// alive, which could be 0, if the user specified 0 for the property
173179
assert keepAliveTimeout >= 0;
174180
if (keepAliveTimeout == 0) {
175-
http.closeServer();
181+
oldClient = http;
176182
} else {
177183
v = new ClientVector(keepAliveTimeout * 1000);
178184
v.put(http);
179185
super.put(key, v);
180186
}
181187
} else {
182-
v.put(http);
188+
oldClient = v.put(http);
183189
}
184190
} finally {
185191
cacheLock.unlock();
186192
}
193+
// close after releasing locks
194+
if (oldClient != null) {
195+
oldClient.closeServer();
196+
}
187197
}
188198

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

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

247241
// Remove all outdated HttpClients.
248242
cacheLock.lock();
@@ -254,15 +248,18 @@ public void run() {
254248
ClientVector v = get(key);
255249
v.lock();
256250
try {
257-
KeepAliveEntry e = v.peek();
251+
KeepAliveEntry e = v.peekLast();
258252
while (e != null) {
259253
if ((currentTime - e.idleStartTime) > v.nap) {
260-
v.poll();
261-
e.hc.closeServer();
254+
v.pollLast();
255+
if (closeList == null) {
256+
closeList = new ArrayList<>();
257+
}
258+
closeList.add(e.hc);
262259
} else {
263260
break;
264261
}
265-
e = v.peek();
262+
e = v.peekLast();
266263
}
267264

268265
if (v.isEmpty()) {
@@ -278,6 +275,12 @@ public void run() {
278275
}
279276
} finally {
280277
cacheLock.unlock();
278+
// close connections outside cacheLock
279+
if (closeList != null) {
280+
for (HttpClient hc : closeList) {
281+
hc.closeServer();
282+
}
283+
}
281284
}
282285
} while (!isEmpty());
283286
}
@@ -298,8 +301,8 @@ private void readObject(ObjectInputStream stream)
298301
}
299302
}
300303

301-
/* FILO order for recycling HttpClients, should run in a thread
302-
* to time them out. If > maxConns are in use, block.
304+
/* LIFO order for reusing HttpClients. Most recent entries at the front.
305+
* If > maxConns are in use, discard oldest.
303306
*/
304307
class ClientVector extends ArrayDeque<KeepAliveEntry> {
305308
@java.io.Serial
@@ -313,62 +316,47 @@ class ClientVector extends ArrayDeque<KeepAliveEntry> {
313316
this.nap = nap;
314317
}
315318

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

359-
/* remove an HttpClient */
360-
boolean remove(HttpClient h) {
345+
HttpClient put(HttpClient h) {
346+
HttpClient staleClient = null;
361347
lock();
362348
try {
363-
for (KeepAliveEntry curr : this) {
364-
if (curr.hc == h) {
365-
return super.remove(curr);
366-
}
349+
assert KeepAliveCache.getMaxConnections() > 0;
350+
if (size() >= KeepAliveCache.getMaxConnections()) {
351+
// remove oldest connection
352+
staleClient = removeLast().hc;
367353
}
368-
return false;
354+
addFirst(new KeepAliveEntry(h, System.currentTimeMillis()));
369355
} finally {
370356
unlock();
371357
}
358+
// close after releasing the locks
359+
return staleClient;
372360
}
373361

374362
final void lock() {
@@ -396,10 +384,10 @@ private void readObject(ObjectInputStream stream)
396384
}
397385

398386
class KeepAliveKey {
399-
private String protocol = null;
400-
private String host = null;
401-
private int port = 0;
402-
private Object obj = null; // additional key, such as socketfactory
387+
private final String protocol;
388+
private final String host;
389+
private final int port;
390+
private final Object obj; // additional key, such as socketfactory
403391

404392
/**
405393
* Constructor
@@ -440,8 +428,8 @@ public int hashCode() {
440428
}
441429

442430
class KeepAliveEntry {
443-
HttpClient hc;
444-
long idleStartTime;
431+
final HttpClient hc;
432+
final long idleStartTime;
445433

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

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

+9
Original file line numberDiff line numberDiff line change
@@ -434,6 +434,15 @@ protected Socket createSocket() throws IOException {
434434
}
435435
}
436436

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

438447
@Override
439448
public boolean needsTunneling() {

0 commit comments

Comments
 (0)