@@ -122,6 +122,12 @@ public KeepAliveCache() {}
122
122
*/
123
123
@ SuppressWarnings ("removal" )
124
124
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 ;
125
131
cacheLock .lock ();
126
132
try {
127
133
boolean startThread = (keepAliveTimer == null );
@@ -172,42 +178,29 @@ public Void run() {
172
178
// alive, which could be 0, if the user specified 0 for the property
173
179
assert keepAliveTimeout >= 0 ;
174
180
if (keepAliveTimeout == 0 ) {
175
- http . closeServer () ;
181
+ oldClient = http ;
176
182
} else {
177
183
v = new ClientVector (keepAliveTimeout * 1000 );
178
184
v .put (http );
179
185
super .put (key , v );
180
186
}
181
187
} else {
182
- v .put (http );
188
+ oldClient = v .put (http );
183
189
}
184
190
} finally {
185
191
cacheLock .unlock ();
186
192
}
193
+ // close after releasing locks
194
+ if (oldClient != null ) {
195
+ oldClient .closeServer ();
196
+ }
187
197
}
188
198
189
199
// returns the keep alive set by user in system property or -1 if not set
190
200
private static int getUserKeepAlive (boolean isProxy ) {
191
201
return isProxy ? userKeepAliveProxy : userKeepAliveServer ;
192
202
}
193
203
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
-
211
204
/* called by a clientVector thread when all its connections have timed out
212
205
* and that vector of connections should be removed.
213
206
*/
@@ -243,6 +236,7 @@ public void run() {
243
236
try {
244
237
Thread .sleep (LIFETIME );
245
238
} catch (InterruptedException e ) {}
239
+ List <HttpClient > closeList = null ;
246
240
247
241
// Remove all outdated HttpClients.
248
242
cacheLock .lock ();
@@ -254,15 +248,18 @@ public void run() {
254
248
ClientVector v = get (key );
255
249
v .lock ();
256
250
try {
257
- KeepAliveEntry e = v .peek ();
251
+ KeepAliveEntry e = v .peekLast ();
258
252
while (e != null ) {
259
253
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 );
262
259
} else {
263
260
break ;
264
261
}
265
- e = v .peek ();
262
+ e = v .peekLast ();
266
263
}
267
264
268
265
if (v .isEmpty ()) {
@@ -278,6 +275,12 @@ public void run() {
278
275
}
279
276
} finally {
280
277
cacheLock .unlock ();
278
+ // close connections outside cacheLock
279
+ if (closeList != null ) {
280
+ for (HttpClient hc : closeList ) {
281
+ hc .closeServer ();
282
+ }
283
+ }
281
284
}
282
285
} while (!isEmpty ());
283
286
}
@@ -298,8 +301,8 @@ private void readObject(ObjectInputStream stream)
298
301
}
299
302
}
300
303
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 .
303
306
*/
304
307
class ClientVector extends ArrayDeque <KeepAliveEntry > {
305
308
@ java .io .Serial
@@ -313,62 +316,47 @@ class ClientVector extends ArrayDeque<KeepAliveEntry> {
313
316
this .nap = nap ;
314
317
}
315
318
319
+ /* return a still valid, idle HttpClient */
316
320
HttpClient get () {
317
321
lock ();
318
322
try {
319
- if (isEmpty ()) {
323
+ // check the most recent connection, use if still valid
324
+ KeepAliveEntry e = peekFirst ();
325
+ if (e == null ) {
320
326
return null ;
321
327
}
322
-
323
- // Loop until we find a connection that has not timed out
324
- HttpClient hc = null ;
325
328
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
351
331
} 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 ;
353
339
}
354
340
} finally {
355
341
unlock ();
356
342
}
357
343
}
358
344
359
- /* remove an HttpClient */
360
- boolean remove ( HttpClient h ) {
345
+ HttpClient put ( HttpClient h ) {
346
+ HttpClient staleClient = null ;
361
347
lock ();
362
348
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 ;
367
353
}
368
- return false ;
354
+ addFirst ( new KeepAliveEntry ( h , System . currentTimeMillis ())) ;
369
355
} finally {
370
356
unlock ();
371
357
}
358
+ // close after releasing the locks
359
+ return staleClient ;
372
360
}
373
361
374
362
final void lock () {
@@ -396,10 +384,10 @@ private void readObject(ObjectInputStream stream)
396
384
}
397
385
398
386
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
403
391
404
392
/**
405
393
* Constructor
@@ -440,8 +428,8 @@ public int hashCode() {
440
428
}
441
429
442
430
class KeepAliveEntry {
443
- HttpClient hc ;
444
- long idleStartTime ;
431
+ final HttpClient hc ;
432
+ final long idleStartTime ;
445
433
446
434
KeepAliveEntry (HttpClient hc , long idleStartTime ) {
447
435
this .hc = hc ;
0 commit comments