@@ -113,38 +113,45 @@ public KeepAliveCache() {}
113113 * @param url The URL contains info about the host and port
114114 * @param http The HttpClient to be cached
115115 */
116- public synchronized void put (final URL url , Object obj , HttpClient http ) {
117- boolean startThread = (keepAliveTimer == null );
118- if (!startThread ) {
119- if (!keepAliveTimer .isAlive ()) {
120- startThread = true ;
121- }
122- }
123- if (startThread ) {
124- clear ();
125- /* Unfortunately, we can't always believe the keep-alive timeout we got
126- * back from the server. If I'm connected through a Netscape proxy
127- * to a server that sent me a keep-alive
128- * time of 15 sec, the proxy unilaterally terminates my connection
129- * The robustness to get around this is in HttpClient.parseHTTP()
130- */
131- final KeepAliveCache cache = this ;
132- AccessController .doPrivileged (new PrivilegedAction <>() {
133- public Void run () {
134- keepAliveTimer = InnocuousThread .newSystemThread ("Keep-Alive-Timer" , cache );
135- keepAliveTimer .setDaemon (true );
136- keepAliveTimer .setPriority (Thread .MAX_PRIORITY - 2 );
137- keepAliveTimer .start ();
138- return null ;
116+ public void put (final URL url , Object obj , HttpClient http ) {
117+ // this method may need to close an HttpClient, either because
118+ // it is not cacheable, or because the cache is at its capacity.
119+ // In the latter case, we close the least recently used client.
120+ // The client to close is stored in oldClient, and is closed
121+ // after cacheLock is released.
122+ HttpClient oldClient = null ;
123+ synchronized (this ) {
124+ boolean startThread = (keepAliveTimer == null );
125+ if (!startThread ) {
126+ if (!keepAliveTimer .isAlive ()) {
127+ startThread = true ;
139128 }
140- });
141- }
129+ }
130+ if (startThread ) {
131+ clear ();
132+ /* Unfortunately, we can't always believe the keep-alive timeout we got
133+ * back from the server. If I'm connected through a Netscape proxy
134+ * to a server that sent me a keep-alive
135+ * time of 15 sec, the proxy unilaterally terminates my connection
136+ * The robustness to get around this is in HttpClient.parseHTTP()
137+ */
138+ final KeepAliveCache cache = this ;
139+ AccessController .doPrivileged (new PrivilegedAction <>() {
140+ public Void run () {
141+ keepAliveTimer = InnocuousThread .newSystemThread ("Keep-Alive-Timer" , cache );
142+ keepAliveTimer .setDaemon (true );
143+ keepAliveTimer .setPriority (Thread .MAX_PRIORITY - 2 );
144+ keepAliveTimer .start ();
145+ return null ;
146+ }
147+ });
148+ }
142149
143- KeepAliveKey key = new KeepAliveKey (url , obj );
144- ClientVector v = super .get (key );
150+ KeepAliveKey key = new KeepAliveKey (url , obj );
151+ ClientVector v = super .get (key );
145152
146- if (v == null ) {
147- int keepAliveTimeout = http .getKeepAliveTimeout ();
153+ if (v == null ) {
154+ int keepAliveTimeout = http .getKeepAliveTimeout ();
148155 if (keepAliveTimeout == 0 ) {
149156 keepAliveTimeout = getUserKeepAlive (http .getUsingProxy ());
150157 if (keepAliveTimeout == -1 ) {
@@ -164,14 +171,19 @@ public Void run() {
164171 // alive, which could be 0, if the user specified 0 for the property
165172 assert keepAliveTimeout >= 0 ;
166173 if (keepAliveTimeout == 0 ) {
167- http . closeServer () ;
174+ oldClient = http ;
168175 } else {
169176 v = new ClientVector (keepAliveTimeout * 1000 );
170177 v .put (http );
171178 super .put (key , v );
172179 }
173- } else {
174- v .put (http );
180+ } else {
181+ oldClient = v .put (http );
182+ }
183+ }
184+ // close after releasing locks
185+ if (oldClient != null ) {
186+ oldClient .closeServer ();
175187 }
176188 }
177189
@@ -221,6 +233,7 @@ public void run() {
221233 try {
222234 Thread .sleep (LIFETIME );
223235 } catch (InterruptedException e ) {}
236+ List <HttpClient > closeList = null ;
224237
225238 // Remove all outdated HttpClients.
226239 synchronized (this ) {
@@ -230,15 +243,18 @@ public void run() {
230243 for (KeepAliveKey key : keySet ()) {
231244 ClientVector v = get (key );
232245 synchronized (v ) {
233- KeepAliveEntry e = v .peek ();
246+ KeepAliveEntry e = v .peekLast ();
234247 while (e != null ) {
235248 if ((currentTime - e .idleStartTime ) > v .nap ) {
236- v .poll ();
237- e .hc .closeServer ();
249+ v .pollLast ();
250+ if (closeList == null ) {
251+ closeList = new ArrayList <>();
252+ }
253+ closeList .add (e .hc );
238254 } else {
239255 break ;
240256 }
241- e = v .peek ();
257+ e = v .peekLast ();
242258 }
243259
244260 if (v .isEmpty ()) {
@@ -251,6 +267,12 @@ public void run() {
251267 removeVector (key );
252268 }
253269 }
270+ // close connections outside cacheLock
271+ if (closeList != null ) {
272+ for (HttpClient hc : closeList ) {
273+ hc .closeServer ();
274+ }
275+ }
254276 } while (!isEmpty ());
255277 }
256278
@@ -268,8 +290,8 @@ private void readObject(ObjectInputStream stream)
268290 }
269291}
270292
271- /* FILO order for recycling HttpClients, should run in a thread
272- * to time them out. If > maxConns are in use, block .
293+ /* LIFO order for reusing HttpClients. Most recent entries at the front.
294+ * If > maxConns are in use, discard oldest .
273295 */
274296class ClientVector extends ArrayDeque <KeepAliveEntry > {
275297 private static final long serialVersionUID = -8680532108106489459L ;
@@ -282,36 +304,37 @@ class ClientVector extends ArrayDeque<KeepAliveEntry> {
282304 }
283305
284306 synchronized HttpClient get () {
285- if (isEmpty ()) {
307+ // check the most recent connection, use if still valid
308+ KeepAliveEntry e = peekFirst ();
309+ if (e == null ) {
286310 return null ;
287311 }
288312
289- // Loop until we find a connection that has not timed out
290- HttpClient hc = null ;
291313 long currentTime = System .currentTimeMillis ();
292- do {
293- KeepAliveEntry e = pop ();
294- if ((currentTime - e .idleStartTime ) > nap ) {
295- e .hc .closeServer ();
296- } else {
297- hc = e .hc ;
298- if (KeepAliveCache .logger .isLoggable (PlatformLogger .Level .FINEST )) {
299- String msg = "cached HttpClient was idle for "
314+ if ((currentTime - e .idleStartTime ) > nap ) {
315+ return null ; // all connections stale - will be cleaned up later
316+ } else {
317+ pollFirst ();
318+ if (KeepAliveCache .logger .isLoggable (PlatformLogger .Level .FINEST )) {
319+ String msg = "cached HttpClient was idle for "
300320 + Long .toString (currentTime - e .idleStartTime );
301- KeepAliveCache .logger .finest (msg );
302- }
321+ KeepAliveCache .logger .finest (msg );
303322 }
304- } while (( hc == null ) && (! isEmpty ())) ;
305- return hc ;
323+ return e . hc ;
324+ }
306325 }
307326
308327 /* return a still valid, unused HttpClient */
309- synchronized void put (HttpClient h ) {
328+ synchronized HttpClient put (HttpClient h ) {
329+ HttpClient staleClient = null ;
330+ assert KeepAliveCache .getMaxConnections () > 0 ;
310331 if (size () >= KeepAliveCache .getMaxConnections ()) {
311- h .closeServer (); // otherwise the connection remains in limbo
312- } else {
313- push (new KeepAliveEntry (h , System .currentTimeMillis ()));
332+ // remove oldest connection
333+ staleClient = removeLast ().hc ;
314334 }
335+ addFirst (new KeepAliveEntry (h , System .currentTimeMillis ()));
336+ // close after releasing the locks
337+ return staleClient ;
315338 }
316339
317340 /* remove an HttpClient */
@@ -339,10 +362,10 @@ private void readObject(ObjectInputStream stream)
339362}
340363
341364class KeepAliveKey {
342- private String protocol = null ;
343- private String host = null ;
344- private int port = 0 ;
345- private Object obj = null ; // additional key, such as socketfactory
365+ private final String protocol ;
366+ private final String host ;
367+ private final int port ;
368+ private final Object obj ; // additional key, such as socketfactory
346369
347370 /**
348371 * Constructor
@@ -383,8 +406,8 @@ public int hashCode() {
383406}
384407
385408class KeepAliveEntry {
386- HttpClient hc ;
387- long idleStartTime ;
409+ final HttpClient hc ;
410+ final long idleStartTime ;
388411
389412 KeepAliveEntry (HttpClient hc , long idleStartTime ) {
390413 this .hc = hc ;
0 commit comments