@@ -112,49 +112,56 @@ public KeepAliveCache() {}
112112 * @param url The URL contains info about the host and port
113113 * @param http The HttpClient to be cached
114114 */
115- public synchronized void put (final URL url , Object obj , HttpClient http ) {
116- boolean startThread = (keepAliveTimer == null );
117- if (!startThread ) {
118- if (!keepAliveTimer .isAlive ()) {
119- startThread = true ;
115+ public void put (final URL url , Object obj , HttpClient http ) {
116+ // this method may need to close an HttpClient, either because
117+ // it is not cacheable, or because the cache is at its capacity.
118+ // In the latter case, we close the least recently used client.
119+ // The client to close is stored in oldClient, and is closed
120+ // after cacheLock is released.
121+ HttpClient oldClient = null ;
122+ synchronized (this ) {
123+ boolean startThread = (keepAliveTimer == null );
124+ if (!startThread ) {
125+ if (!keepAliveTimer .isAlive ()) {
126+ startThread = true ;
127+ }
120128 }
121- }
122- if (startThread ) {
123- clear ();
124- /* Unfortunately, we can't always believe the keep-alive timeout we got
125- * back from the server. If I'm connected through a Netscape proxy
126- * to a server that sent me a keep-alive
127- * time of 15 sec, the proxy unilaterally terminates my connection
128- * The robustness to get around this is in HttpClient.parseHTTP()
129- */
130- final KeepAliveCache cache = this ;
131- AccessController .doPrivileged (new PrivilegedAction <Void >() {
132- public Void run () {
133- // We want to create the Keep-Alive-Timer in the
134- // system threadgroup
135- ThreadGroup grp = Thread .currentThread ().getThreadGroup ();
136- ThreadGroup parent = null ;
137- while ((parent = grp .getParent ()) != null ) {
138- grp = parent ;
139- }
129+ if (startThread ) {
130+ clear ();
131+ /* Unfortunately, we can't always believe the keep-alive timeout we got
132+ * back from the server. If I'm connected through a Netscape proxy
133+ * to a server that sent me a keep-alive
134+ * time of 15 sec, the proxy unilaterally terminates my connection
135+ * The robustness to get around this is in HttpClient.parseHTTP()
136+ */
137+ final KeepAliveCache cache = this ;
138+ AccessController .doPrivileged (new PrivilegedAction <Void >() {
139+ public Void run () {
140+ // We want to create the Keep-Alive-Timer in the
141+ // system threadgroup
142+ ThreadGroup grp = Thread .currentThread ().getThreadGroup ();
143+ ThreadGroup parent = null ;
144+ while ((parent = grp .getParent ()) != null ) {
145+ grp = parent ;
146+ }
140147
141- keepAliveTimer = new Thread (grp , cache , "Keep-Alive-Timer" );
142- keepAliveTimer .setDaemon (true );
143- keepAliveTimer .setPriority (Thread .MAX_PRIORITY - 2 );
144- // Set the context class loader to null in order to avoid
145- // keeping a strong reference to an application classloader.
146- keepAliveTimer .setContextClassLoader (null );
147- keepAliveTimer .start ();
148- return null ;
149- }
150- });
151- }
148+ keepAliveTimer = new Thread (grp , cache , "Keep-Alive-Timer" );
149+ keepAliveTimer .setDaemon (true );
150+ keepAliveTimer .setPriority (Thread .MAX_PRIORITY - 2 );
151+ // Set the context class loader to null in order to avoid
152+ // keeping a strong reference to an application classloader.
153+ keepAliveTimer .setContextClassLoader (null );
154+ keepAliveTimer .start ();
155+ return null ;
156+ }
157+ });
158+ }
152159
153- KeepAliveKey key = new KeepAliveKey (url , obj );
154- ClientVector v = super .get (key );
160+ KeepAliveKey key = new KeepAliveKey (url , obj );
161+ ClientVector v = super .get (key );
155162
156- if (v == null ) {
157- int keepAliveTimeout = http .getKeepAliveTimeout ();
163+ if (v == null ) {
164+ int keepAliveTimeout = http .getKeepAliveTimeout ();
158165 if (keepAliveTimeout == 0 ) {
159166 keepAliveTimeout = getUserKeepAlive (http .getUsingProxy ());
160167 if (keepAliveTimeout == -1 ) {
@@ -174,14 +181,19 @@ public Void run() {
174181 // alive, which could be 0, if the user specified 0 for the property
175182 assert keepAliveTimeout >= 0 ;
176183 if (keepAliveTimeout == 0 ) {
177- http . closeServer () ;
184+ oldClient = http ;
178185 } else {
179186 v = new ClientVector (keepAliveTimeout * 1000 );
180187 v .put (http );
181188 super .put (key , v );
182189 }
183- } else {
184- v .put (http );
190+ } else {
191+ oldClient = v .put (http );
192+ }
193+ }
194+ // close after releasing locks
195+ if (oldClient != null ) {
196+ oldClient .closeServer ();
185197 }
186198 }
187199
@@ -213,7 +225,6 @@ synchronized void removeVector(KeepAliveKey k) {
213225 * Check to see if this URL has a cached HttpClient
214226 */
215227 public synchronized HttpClient get (URL url , Object obj ) {
216-
217228 KeepAliveKey key = new KeepAliveKey (url , obj );
218229 ClientVector v = super .get (key );
219230 if (v == null ) { // nothing in cache yet
@@ -232,6 +243,7 @@ public void run() {
232243 try {
233244 Thread .sleep (LIFETIME );
234245 } catch (InterruptedException e ) {}
246+ List <HttpClient > closeList = null ;
235247
236248 // Remove all outdated HttpClients.
237249 synchronized (this ) {
@@ -241,15 +253,18 @@ public void run() {
241253 for (KeepAliveKey key : keySet ()) {
242254 ClientVector v = get (key );
243255 synchronized (v ) {
244- KeepAliveEntry e = v .peek ();
256+ KeepAliveEntry e = v .peekLast ();
245257 while (e != null ) {
246258 if ((currentTime - e .idleStartTime ) > v .nap ) {
247- v .poll ();
248- e .hc .closeServer ();
259+ v .pollLast ();
260+ if (closeList == null ) {
261+ closeList = new ArrayList <>();
262+ }
263+ closeList .add (e .hc );
249264 } else {
250265 break ;
251266 }
252- e = v .peek ();
267+ e = v .peekLast ();
253268 }
254269
255270 if (v .isEmpty ()) {
@@ -262,6 +277,12 @@ public void run() {
262277 removeVector (key );
263278 }
264279 }
280+ // close connections outside cacheLock
281+ if (closeList != null ) {
282+ for (HttpClient hc : closeList ) {
283+ hc .closeServer ();
284+ }
285+ }
265286 } while (!isEmpty ());
266287 }
267288
@@ -279,8 +300,8 @@ private void readObject(ObjectInputStream stream)
279300 }
280301}
281302
282- /* FILO order for recycling HttpClients, should run in a thread
283- * 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 .
284305 */
285306class ClientVector extends ArrayDeque <KeepAliveEntry > {
286307 private static final long serialVersionUID = -8680532108106489459L ;
@@ -293,36 +314,37 @@ class ClientVector extends ArrayDeque<KeepAliveEntry> {
293314 }
294315
295316 synchronized HttpClient get () {
296- if (isEmpty ()) {
317+ // check the most recent connection, use if still valid
318+ KeepAliveEntry e = peekFirst ();
319+ if (e == null ) {
297320 return null ;
298321 }
299322
300- // Loop until we find a connection that has not timed out
301- HttpClient hc = null ;
302323 long currentTime = System .currentTimeMillis ();
303- do {
304- KeepAliveEntry e = pop ();
305- if ((currentTime - e .idleStartTime ) > nap ) {
306- e .hc .closeServer ();
307- } else {
308- hc = e .hc ;
309- if (KeepAliveCache .logger .isLoggable (PlatformLogger .Level .FINEST )) {
310- String msg = "cached HttpClient was idle for "
324+ if ((currentTime - e .idleStartTime ) > nap ) {
325+ return null ; // all connections stale - will be cleaned up later
326+ } else {
327+ pollFirst ();
328+ if (KeepAliveCache .logger .isLoggable (PlatformLogger .Level .FINEST )) {
329+ String msg = "cached HttpClient was idle for "
311330 + Long .toString (currentTime - e .idleStartTime );
312- KeepAliveCache .logger .finest (msg );
313- }
331+ KeepAliveCache .logger .finest (msg );
314332 }
315- } while (( hc == null ) && (! isEmpty ())) ;
316- return hc ;
333+ return e . hc ;
334+ }
317335 }
318336
319337 /* return a still valid, unused HttpClient */
320- synchronized void put (HttpClient h ) {
338+ synchronized HttpClient put (HttpClient h ) {
339+ HttpClient staleClient = null ;
340+ assert KeepAliveCache .getMaxConnections () > 0 ;
321341 if (size () >= KeepAliveCache .getMaxConnections ()) {
322- h .closeServer (); // otherwise the connection remains in limbo
323- } else {
324- push (new KeepAliveEntry (h , System .currentTimeMillis ()));
342+ // remove oldest connection
343+ staleClient = removeLast ().hc ;
325344 }
345+ addFirst (new KeepAliveEntry (h , System .currentTimeMillis ()));
346+ // close after releasing the locks
347+ return staleClient ;
326348 }
327349
328350 /* remove an HttpClient */
@@ -350,10 +372,10 @@ private void readObject(ObjectInputStream stream)
350372}
351373
352374class KeepAliveKey {
353- private String protocol = null ;
354- private String host = null ;
355- private int port = 0 ;
356- private Object obj = null ; // additional key, such as socketfactory
375+ private final String protocol ;
376+ private final String host ;
377+ private final int port ;
378+ private final Object obj ; // additional key, such as socketfactory
357379
358380 /**
359381 * Constructor
@@ -394,8 +416,8 @@ public int hashCode() {
394416}
395417
396418class KeepAliveEntry {
397- HttpClient hc ;
398- long idleStartTime ;
419+ final HttpClient hc ;
420+ final long idleStartTime ;
399421
400422 KeepAliveEntry (HttpClient hc , long idleStartTime ) {
401423 this .hc = hc ;
0 commit comments