@@ -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 */
303306class 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
397385class 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
441429class 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 ;
0 commit comments