51 */ |
51 */ |
52 final class ConnectionPool { |
52 final class ConnectionPool { |
53 |
53 |
54 static final long KEEP_ALIVE = Utils.getIntegerNetProperty( |
54 static final long KEEP_ALIVE = Utils.getIntegerNetProperty( |
55 "jdk.httpclient.keepalive.timeout", 1200); // seconds |
55 "jdk.httpclient.keepalive.timeout", 1200); // seconds |
|
56 static final long MAX_POOL_SIZE = Utils.getIntegerNetProperty( |
|
57 "jdk.httpclient.connectionPoolSize", 0); // unbounded |
56 final Logger debug = Utils.getDebugLogger(this::dbgString, Utils.DEBUG); |
58 final Logger debug = Utils.getDebugLogger(this::dbgString, Utils.DEBUG); |
57 |
59 |
58 // Pools of idle connections |
60 // Pools of idle connections |
59 |
61 |
60 private final HashMap<CacheKey,LinkedList<HttpConnection>> plainPool; |
62 private final HashMap<CacheKey,LinkedList<HttpConnection>> plainPool; |
158 // since we don't want to trigger the cleanup if the connection |
160 // since we don't want to trigger the cleanup if the connection |
159 // is not in the pool. |
161 // is not in the pool. |
160 CleanupTrigger cleanup = registerCleanupTrigger(conn); |
162 CleanupTrigger cleanup = registerCleanupTrigger(conn); |
161 |
163 |
162 // it's possible that cleanup may have been called. |
164 // it's possible that cleanup may have been called. |
|
165 HttpConnection toClose = null; |
163 synchronized(this) { |
166 synchronized(this) { |
164 if (cleanup.isDone()) { |
167 if (cleanup.isDone()) { |
165 return; |
168 return; |
166 } else if (stopped) { |
169 } else if (stopped) { |
167 conn.close(); |
170 conn.close(); |
168 return; |
171 return; |
169 } |
172 } |
|
173 if (MAX_POOL_SIZE > 0 && expiryList.size() >= MAX_POOL_SIZE) { |
|
174 toClose = expiryList.removeOldest(); |
|
175 if (toClose != null) removeFromPool(toClose); |
|
176 } |
170 if (conn instanceof PlainHttpConnection) { |
177 if (conn instanceof PlainHttpConnection) { |
171 putConnection(conn, plainPool); |
178 putConnection(conn, plainPool); |
172 } else { |
179 } else { |
173 assert conn.isSecure(); |
180 assert conn.isSecure(); |
174 putConnection(conn, sslPool); |
181 putConnection(conn, sslPool); |
175 } |
182 } |
176 expiryList.add(conn, now, keepAlive); |
183 expiryList.add(conn, now, keepAlive); |
|
184 } |
|
185 if (toClose != null) { |
|
186 if (debug.on()) { |
|
187 debug.log("Maximum pool size reached: removing oldest connection %s", |
|
188 toClose.dbgString()); |
|
189 } |
|
190 close(toClose); |
177 } |
191 } |
178 //System.out.println("Return to pool: " + conn); |
192 //System.out.println("Return to pool: " + conn); |
179 } |
193 } |
180 |
194 |
181 private CleanupTrigger registerCleanupTrigger(HttpConnection conn) { |
195 private CleanupTrigger registerCleanupTrigger(HttpConnection conn) { |
312 */ |
326 */ |
313 private static final class ExpiryList { |
327 private static final class ExpiryList { |
314 private final LinkedList<ExpiryEntry> list = new LinkedList<>(); |
328 private final LinkedList<ExpiryEntry> list = new LinkedList<>(); |
315 private volatile boolean mayContainEntries; |
329 private volatile boolean mayContainEntries; |
316 |
330 |
|
331 int size() { return list.size(); } |
|
332 |
317 // A loosely accurate boolean whose value is computed |
333 // A loosely accurate boolean whose value is computed |
318 // at the end of each operation performed on ExpiryList; |
334 // at the end of each operation performed on ExpiryList; |
319 // Does not require synchronizing on the ConnectionPool. |
335 // Does not require synchronizing on the ConnectionPool. |
320 boolean purgeMaybeRequired() { |
336 boolean purgeMaybeRequired() { |
321 return mayContainEntries; |
337 return mayContainEntries; |
325 // should only be called while holding a synchronization |
341 // should only be called while holding a synchronization |
326 // lock on the ConnectionPool |
342 // lock on the ConnectionPool |
327 Optional<Instant> nextExpiryDeadline() { |
343 Optional<Instant> nextExpiryDeadline() { |
328 if (list.isEmpty()) return Optional.empty(); |
344 if (list.isEmpty()) return Optional.empty(); |
329 else return Optional.of(list.getLast().expiry); |
345 else return Optional.of(list.getLast().expiry); |
|
346 } |
|
347 |
|
348 // should only be called while holding a synchronization |
|
349 // lock on the ConnectionPool |
|
350 HttpConnection removeOldest() { |
|
351 ExpiryEntry entry = list.pollLast(); |
|
352 return entry == null ? null : entry.connection; |
330 } |
353 } |
331 |
354 |
332 // should only be called while holding a synchronization |
355 // should only be called while holding a synchronization |
333 // lock on the ConnectionPool |
356 // lock on the ConnectionPool |
334 void add(HttpConnection conn) { |
357 void add(HttpConnection conn) { |
417 list.clear(); |
440 list.clear(); |
418 mayContainEntries = false; |
441 mayContainEntries = false; |
419 } |
442 } |
420 } |
443 } |
421 |
444 |
|
445 // Remove a connection from the pool. |
|
446 // should only be called while holding a synchronization |
|
447 // lock on the ConnectionPool |
|
448 private void removeFromPool(HttpConnection c) { |
|
449 assert Thread.holdsLock(this); |
|
450 if (c instanceof PlainHttpConnection) { |
|
451 removeFromPool(c, plainPool); |
|
452 } else { |
|
453 assert c.isSecure(); |
|
454 removeFromPool(c, sslPool); |
|
455 } |
|
456 } |
|
457 |
|
458 // Used by tests |
|
459 synchronized boolean contains(HttpConnection c) { |
|
460 final CacheKey key = c.cacheKey(); |
|
461 List<HttpConnection> list; |
|
462 if ((list = plainPool.get(key)) != null) { |
|
463 if (list.contains(c)) return true; |
|
464 } |
|
465 if ((list = sslPool.get(key)) != null) { |
|
466 if (list.contains(c)) return true; |
|
467 } |
|
468 return false; |
|
469 } |
|
470 |
422 void cleanup(HttpConnection c, Throwable error) { |
471 void cleanup(HttpConnection c, Throwable error) { |
423 if (debug.on()) |
472 if (debug.on()) |
424 debug.log("%s : ConnectionPool.cleanup(%s)", |
473 debug.log("%s : ConnectionPool.cleanup(%s)", |
425 String.valueOf(c.getConnectionFlow()), error); |
474 String.valueOf(c.getConnectionFlow()), error); |
426 synchronized(this) { |
475 synchronized(this) { |
427 if (c instanceof PlainHttpConnection) { |
476 removeFromPool(c); |
428 removeFromPool(c, plainPool); |
|
429 } else { |
|
430 assert c.isSecure(); |
|
431 removeFromPool(c, sslPool); |
|
432 } |
|
433 expiryList.remove(c); |
477 expiryList.remove(c); |
434 } |
478 } |
435 c.close(); |
479 c.close(); |
436 } |
480 } |
437 |
481 |