1 /* 2 * Copyright (c) 2015, 2016, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package jdk.incubator.http; 27 28 import java.lang.ref.WeakReference; 29 import java.net.InetSocketAddress; 30 import java.util.HashMap; 31 import java.util.LinkedList; 32 import java.util.ListIterator; 33 import java.util.Objects; 34 import java.util.concurrent.atomic.AtomicLong; 35 import java.util.concurrent.atomic.AtomicReference; 36 import jdk.incubator.http.internal.common.Utils; 37 38 /** 39 * Http 1.1 connection pool. 40 */ 41 final class ConnectionPool { 42 43 // These counters are used to distribute ids for debugging 44 // The ACTIVE_CLEANER_COUNTER will tell how many CacheCleaner 45 // are active at a given time. It will increase when a new 46 // CacheCleaner is started and decrease when it exits. 47 static final AtomicLong ACTIVE_CLEANER_COUNTER = new AtomicLong(); 48 // The POOL_IDS_COUNTER increases each time a new ConnectionPool 49 // is created. It may wrap and become negative but will never be 50 // decremented. 51 static final AtomicLong POOL_IDS_COUNTER = new AtomicLong(); 52 // The cleanerCounter is used to name cleaner threads within a 53 // a connection pool, and increments monotically. 54 // It may wrap and become negative but will never be 55 // decremented. 56 final AtomicLong cleanerCounter = new AtomicLong(); 57 58 static final long KEEP_ALIVE = Utils.getIntegerNetProperty( 59 "jdk.httpclient.keepalive.timeout", 1200); // seconds 60 61 // Pools of idle connections 62 63 final HashMap<CacheKey,LinkedList<HttpConnection>> plainPool; 64 final HashMap<CacheKey,LinkedList<HttpConnection>> sslPool; 65 // A monotically increasing id for this connection pool. 66 // It may be negative (that's OK) 67 // Mostly used for debugging purposes when looking at thread dumps. 68 // Global scope. 69 final long poolID = POOL_IDS_COUNTER.incrementAndGet(); 70 final AtomicReference<CacheCleaner> cleanerRef; 71 72 /** 73 * Entries in connection pool are keyed by destination address and/or 74 * proxy address: 75 * case 1: plain TCP not via proxy (destination only) 76 * case 2: plain TCP via proxy (proxy only) 77 * case 3: SSL not via proxy (destination only) 78 * case 4: SSL over tunnel (destination and proxy) 79 */ 80 static class CacheKey { 81 final InetSocketAddress proxy; 82 final InetSocketAddress destination; 83 84 CacheKey(InetSocketAddress destination, InetSocketAddress proxy) { 85 this.proxy = proxy; 86 this.destination = destination; 87 } 88 89 @Override 90 public boolean equals(Object obj) { 91 if (obj == null) { 92 return false; 93 } 94 if (getClass() != obj.getClass()) { 95 return false; 96 } 97 final CacheKey other = (CacheKey) obj; 98 if (!Objects.equals(this.proxy, other.proxy)) { 99 return false; 100 } 101 if (!Objects.equals(this.destination, other.destination)) { 102 return false; 103 } 104 return true; 105 } 106 107 @Override 108 public int hashCode() { 109 return Objects.hash(proxy, destination); 110 } 111 } 112 113 static class ExpiryEntry { 114 final HttpConnection connection; 115 final long expiry; // absolute time in seconds of expiry time 116 ExpiryEntry(HttpConnection connection, long expiry) { 117 this.connection = connection; 118 this.expiry = expiry; 119 } 120 } 121 122 final LinkedList<ExpiryEntry> expiryList; 123 124 /** 125 * There should be one of these per HttpClient. 126 */ 127 ConnectionPool() { 128 plainPool = new HashMap<>(); 129 sslPool = new HashMap<>(); 130 expiryList = new LinkedList<>(); 131 cleanerRef = new AtomicReference<>(); 132 } 133 134 void start() { 135 } 136 137 static CacheKey cacheKey(InetSocketAddress destination, 138 InetSocketAddress proxy) 139 { 140 return new CacheKey(destination, proxy); 141 } 142 143 synchronized HttpConnection getConnection(boolean secure, 144 InetSocketAddress addr, 145 InetSocketAddress proxy) { 146 CacheKey key = new CacheKey(addr, proxy); 147 HttpConnection c = secure ? findConnection(key, sslPool) 148 : findConnection(key, plainPool); 149 //System.out.println ("getConnection returning: " + c); 150 return c; 151 } 152 153 /** 154 * Returns the connection to the pool. 155 */ 156 synchronized void returnToPool(HttpConnection conn) { 157 if (conn instanceof PlainHttpConnection) { 158 putConnection(conn, plainPool); 159 } else { 160 putConnection(conn, sslPool); 161 } 162 addToExpiryList(conn); 163 //System.out.println("Return to pool: " + conn); 164 } 165 166 private HttpConnection 167 findConnection(CacheKey key, 168 HashMap<CacheKey,LinkedList<HttpConnection>> pool) { 169 LinkedList<HttpConnection> l = pool.get(key); 170 if (l == null || l.isEmpty()) { 171 return null; 172 } else { 173 HttpConnection c = l.removeFirst(); 174 removeFromExpiryList(c); 175 return c; 176 } 177 } 178 179 /* called from cache cleaner only */ 180 private void 181 removeFromPool(HttpConnection c, 182 HashMap<CacheKey,LinkedList<HttpConnection>> pool) { 183 //System.out.println("cacheCleaner removing: " + c); 184 LinkedList<HttpConnection> l = pool.get(c.cacheKey()); 185 assert l != null; 186 boolean wasPresent = l.remove(c); 187 assert wasPresent; 188 } 189 190 private void 191 putConnection(HttpConnection c, 192 HashMap<CacheKey,LinkedList<HttpConnection>> pool) { 193 CacheKey key = c.cacheKey(); 194 LinkedList<HttpConnection> l = pool.get(key); 195 if (l == null) { 196 l = new LinkedList<>(); 197 pool.put(key, l); 198 } 199 l.add(c); 200 } 201 202 static String makeCleanerName(long poolId, long cleanerId) { 203 return "HTTP-Cache-cleaner-" + poolId + "-" + cleanerId; 204 } 205 206 // only runs while entries exist in cache 207 final static class CacheCleaner extends Thread { 208 209 volatile boolean stopping; 210 // A monotically increasing id. May wrap and become negative (that's OK) 211 // Mostly used for debugging purposes when looking at thread dumps. 212 // Scoped per connection pool. 213 final long cleanerID; 214 // A reference to the owning ConnectionPool. 215 // This reference's referent may become null if the HttpClientImpl 216 // that owns this pool is GC'ed. 217 final WeakReference<ConnectionPool> ownerRef; 218 219 CacheCleaner(ConnectionPool owner) { 220 this(owner, owner.cleanerCounter.incrementAndGet()); 221 } 222 223 CacheCleaner(ConnectionPool owner, long cleanerID) { 224 super(null, null, makeCleanerName(owner.poolID, cleanerID), 0, false); 225 this.cleanerID = cleanerID; 226 this.ownerRef = new WeakReference<>(owner); 227 setDaemon(true); 228 } 229 230 synchronized boolean stopping() { 231 return stopping || ownerRef.get() == null; 232 } 233 234 synchronized void stopCleaner() { 235 stopping = true; 236 } 237 238 @Override 239 public void run() { 240 ACTIVE_CLEANER_COUNTER.incrementAndGet(); 241 try { 242 while (!stopping()) { 243 try { 244 Thread.sleep(3000); 245 } catch (InterruptedException e) {} 246 ConnectionPool owner = ownerRef.get(); 247 if (owner == null) return; 248 owner.cleanCache(this); 249 owner = null; 250 } 251 } finally { 252 ACTIVE_CLEANER_COUNTER.decrementAndGet(); 253 } 254 } 255 } 256 257 synchronized void removeFromExpiryList(HttpConnection c) { 258 if (c == null) { 259 return; 260 } 261 ListIterator<ExpiryEntry> li = expiryList.listIterator(); 262 while (li.hasNext()) { 263 ExpiryEntry e = li.next(); 264 if (e.connection.equals(c)) { 265 li.remove(); 266 return; 267 } 268 } 269 CacheCleaner cleaner = this.cleanerRef.get(); 270 if (expiryList.isEmpty() && cleaner != null) { 271 this.cleanerRef.compareAndSet(cleaner, null); 272 cleaner.stopCleaner(); 273 cleaner.interrupt(); 274 } 275 } 276 277 private void cleanCache(CacheCleaner cleaner) { 278 long now = System.currentTimeMillis() / 1000; 279 LinkedList<HttpConnection> closelist = new LinkedList<>(); 280 281 synchronized (this) { 282 ListIterator<ExpiryEntry> li = expiryList.listIterator(); 283 while (li.hasNext()) { 284 ExpiryEntry entry = li.next(); 285 if (entry.expiry <= now) { 286 li.remove(); 287 HttpConnection c = entry.connection; 288 closelist.add(c); 289 if (c instanceof PlainHttpConnection) { 290 removeFromPool(c, plainPool); 291 } else { 292 removeFromPool(c, sslPool); 293 } 294 } 295 } 296 if (expiryList.isEmpty() && cleaner != null) { 297 this.cleanerRef.compareAndSet(cleaner, null); 298 cleaner.stopCleaner(); 299 } 300 } 301 for (HttpConnection c : closelist) { 302 //System.out.println ("KAC: closing " + c); 303 c.close(); 304 } 305 } 306 307 private synchronized void addToExpiryList(HttpConnection conn) { 308 long now = System.currentTimeMillis() / 1000; 309 long then = now + KEEP_ALIVE; 310 if (expiryList.isEmpty()) { 311 CacheCleaner cleaner = new CacheCleaner(this); 312 if (this.cleanerRef.compareAndSet(null, cleaner)) { 313 cleaner.start(); 314 } 315 expiryList.add(new ExpiryEntry(conn, then)); 316 return; 317 } 318 319 ListIterator<ExpiryEntry> li = expiryList.listIterator(); 320 while (li.hasNext()) { 321 ExpiryEntry entry = li.next(); 322 323 if (then > entry.expiry) { 324 li.previous(); 325 // insert here 326 li.add(new ExpiryEntry(conn, then)); 327 return; 328 } 329 } 330 // first element of list 331 expiryList.add(new ExpiryEntry(conn, then)); 332 } 333 }