@@ -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 */
304307class 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
398386class 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
442430class 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 ;
0 commit comments