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