@@ -113,38 +113,45 @@ public KeepAliveCache() {}
113
113
* @param url The URL contains info about the host and port
114
114
* @param http The HttpClient to be cached
115
115
*/
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 ;
139
128
}
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
+ }
142
149
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 );
145
152
146
- if (v == null ) {
147
- int keepAliveTimeout = http .getKeepAliveTimeout ();
153
+ if (v == null ) {
154
+ int keepAliveTimeout = http .getKeepAliveTimeout ();
148
155
if (keepAliveTimeout == 0 ) {
149
156
keepAliveTimeout = getUserKeepAlive (http .getUsingProxy ());
150
157
if (keepAliveTimeout == -1 ) {
@@ -164,14 +171,19 @@ public Void run() {
164
171
// alive, which could be 0, if the user specified 0 for the property
165
172
assert keepAliveTimeout >= 0 ;
166
173
if (keepAliveTimeout == 0 ) {
167
- http . closeServer () ;
174
+ oldClient = http ;
168
175
} else {
169
176
v = new ClientVector (keepAliveTimeout * 1000 );
170
177
v .put (http );
171
178
super .put (key , v );
172
179
}
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 ();
175
187
}
176
188
}
177
189
@@ -221,6 +233,7 @@ public void run() {
221
233
try {
222
234
Thread .sleep (LIFETIME );
223
235
} catch (InterruptedException e ) {}
236
+ List <HttpClient > closeList = null ;
224
237
225
238
// Remove all outdated HttpClients.
226
239
synchronized (this ) {
@@ -230,15 +243,18 @@ public void run() {
230
243
for (KeepAliveKey key : keySet ()) {
231
244
ClientVector v = get (key );
232
245
synchronized (v ) {
233
- KeepAliveEntry e = v .peek ();
246
+ KeepAliveEntry e = v .peekLast ();
234
247
while (e != null ) {
235
248
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 );
238
254
} else {
239
255
break ;
240
256
}
241
- e = v .peek ();
257
+ e = v .peekLast ();
242
258
}
243
259
244
260
if (v .isEmpty ()) {
@@ -251,6 +267,12 @@ public void run() {
251
267
removeVector (key );
252
268
}
253
269
}
270
+ // close connections outside cacheLock
271
+ if (closeList != null ) {
272
+ for (HttpClient hc : closeList ) {
273
+ hc .closeServer ();
274
+ }
275
+ }
254
276
} while (!isEmpty ());
255
277
}
256
278
@@ -268,8 +290,8 @@ private void readObject(ObjectInputStream stream)
268
290
}
269
291
}
270
292
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 .
273
295
*/
274
296
class ClientVector extends ArrayDeque <KeepAliveEntry > {
275
297
private static final long serialVersionUID = -8680532108106489459L ;
@@ -282,36 +304,37 @@ class ClientVector extends ArrayDeque<KeepAliveEntry> {
282
304
}
283
305
284
306
synchronized HttpClient get () {
285
- if (isEmpty ()) {
307
+ // check the most recent connection, use if still valid
308
+ KeepAliveEntry e = peekFirst ();
309
+ if (e == null ) {
286
310
return null ;
287
311
}
288
312
289
- // Loop until we find a connection that has not timed out
290
- HttpClient hc = null ;
291
313
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 "
300
320
+ Long .toString (currentTime - e .idleStartTime );
301
- KeepAliveCache .logger .finest (msg );
302
- }
321
+ KeepAliveCache .logger .finest (msg );
303
322
}
304
- } while (( hc == null ) && (! isEmpty ())) ;
305
- return hc ;
323
+ return e . hc ;
324
+ }
306
325
}
307
326
308
327
/* 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 ;
310
331
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 ;
314
334
}
335
+ addFirst (new KeepAliveEntry (h , System .currentTimeMillis ()));
336
+ // close after releasing the locks
337
+ return staleClient ;
315
338
}
316
339
317
340
/* remove an HttpClient */
@@ -339,10 +362,10 @@ private void readObject(ObjectInputStream stream)
339
362
}
340
363
341
364
class 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
346
369
347
370
/**
348
371
* Constructor
@@ -383,8 +406,8 @@ public int hashCode() {
383
406
}
384
407
385
408
class KeepAliveEntry {
386
- HttpClient hc ;
387
- long idleStartTime ;
409
+ final HttpClient hc ;
410
+ final long idleStartTime ;
388
411
389
412
KeepAliveEntry (HttpClient hc , long idleStartTime ) {
390
413
this .hc = hc ;
0 commit comments