1 /* 2 * Copyright (c) 2017, 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. 8 * 9 * This code is distributed in the hope that it will be useful, but WITHOUT 10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 11 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 12 * version 2 for more details (a copy is included in the LICENSE file that 13 * accompanied this code). 14 * 15 * You should have received a copy of the GNU General Public License version 16 * 2 along with this work; if not, write to the Free Software Foundation, 17 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 18 * 19 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 20 * or visit www.oracle.com if you need additional information or have any 21 * questions. 22 */ 23 24 package jdk.incubator.http; 25 26 import java.io.IOException; 27 import java.lang.management.ManagementFactory; 28 import java.lang.ref.Reference; 29 import java.lang.ref.ReferenceQueue; 30 import java.lang.ref.WeakReference; 31 import java.net.Authenticator; 32 import java.net.CookieManager; 33 import java.net.InetSocketAddress; 34 import java.net.ProxySelector; 35 import java.nio.ByteBuffer; 36 import java.nio.channels.SocketChannel; 37 import java.util.Optional; 38 import java.util.concurrent.CompletableFuture; 39 import java.util.concurrent.Executor; 40 import javax.net.ssl.SSLContext; 41 import javax.net.ssl.SSLParameters; 42 import jdk.incubator.http.internal.common.ByteBufferReference; 43 44 /** 45 * @summary Verifies that the ConnectionPool won't prevent an HttpClient 46 * from being GC'ed. Verifies that the ConnectionPool has at most 47 * one CacheCleaner thread running. 48 * @bug 8187044 49 * @author danielfuchs 50 */ 51 public class ConnectionPoolTest { 52 53 static long getActiveCleaners() throws ClassNotFoundException { 54 // ConnectionPool.ACTIVE_CLEANER_COUNTER.get() 55 // ConnectionPoolTest.class.getModule().addReads( 56 // Class.forName("java.lang.management.ManagementFactory").getModule()); 57 return java.util.stream.Stream.of(ManagementFactory.getThreadMXBean() 58 .dumpAllThreads(false, false)) 59 .filter(t -> t.getThreadName().startsWith("HTTP-Cache-cleaner")) 60 .count(); 61 } 62 63 public static void main(String[] args) throws Exception { 64 testCacheCleaners(); 65 } 66 67 public static void testCacheCleaners() throws Exception { 68 ConnectionPool pool = new ConnectionPool(); 69 HttpClient client = new HttpClientStub(pool); 70 InetSocketAddress proxy = InetSocketAddress.createUnresolved("bar", 80); 71 System.out.println("Adding 10 connections to pool"); 72 for (int i=0; i<10; i++) { 73 InetSocketAddress addr = InetSocketAddress.createUnresolved("foo"+i, 80); 74 HttpConnection c1 = new HttpConnectionStub(client, addr, proxy, true); 75 pool.returnToPool(c1); 76 } 77 while (getActiveCleaners() == 0) { 78 System.out.println("Waiting for cleaner to start"); 79 Thread.sleep(10); 80 } 81 System.out.println("Active CacheCleaners: " + getActiveCleaners()); 82 if (getActiveCleaners() > 1) { 83 throw new RuntimeException("Too many CacheCleaner active: " 84 + getActiveCleaners()); 85 } 86 System.out.println("Removing 9 connections from pool"); 87 for (int i=0; i<9; i++) { 88 InetSocketAddress addr = InetSocketAddress.createUnresolved("foo"+i, 80); 89 HttpConnection c2 = pool.getConnection(true, addr, proxy); 90 if (c2 == null) { 91 throw new RuntimeException("connection not found for " + addr); 92 } 93 } 94 System.out.println("Active CacheCleaners: " + getActiveCleaners()); 95 if (getActiveCleaners() != 1) { 96 throw new RuntimeException("Wrong number of CacheCleaner active: " 97 + getActiveCleaners()); 98 } 99 System.out.println("Removing last connection from pool"); 100 for (int i=9; i<10; i++) { 101 InetSocketAddress addr = InetSocketAddress.createUnresolved("foo"+i, 80); 102 HttpConnection c2 = pool.getConnection(true, addr, proxy); 103 if (c2 == null) { 104 throw new RuntimeException("connection not found for " + addr); 105 } 106 } 107 System.out.println("Active CacheCleaners: " + getActiveCleaners() 108 + " (may be 0 or may still be 1)"); 109 if (getActiveCleaners() > 1) { 110 throw new RuntimeException("Too many CacheCleaner active: " 111 + getActiveCleaners()); 112 } 113 InetSocketAddress addr = InetSocketAddress.createUnresolved("foo", 80); 114 HttpConnection c = new HttpConnectionStub(client, addr, proxy, true); 115 System.out.println("Adding/Removing one connection from pool 20 times in a loop"); 116 for (int i=0; i<20; i++) { 117 pool.returnToPool(c); 118 HttpConnection c2 = pool.getConnection(true, addr, proxy); 119 if (c2 == null) { 120 throw new RuntimeException("connection not found for " + addr); 121 } 122 if (c2 != c) { 123 throw new RuntimeException("wrong connection found for " + addr); 124 } 125 } 126 if (getActiveCleaners() > 1) { 127 throw new RuntimeException("Too many CacheCleaner active: " 128 + getActiveCleaners()); 129 } 130 ReferenceQueue<HttpClient> queue = new ReferenceQueue<>(); 131 WeakReference<HttpClient> weak = new WeakReference<>(client, queue); 132 System.gc(); 133 Reference.reachabilityFence(pool); 134 client = null; pool = null; c = null; 135 while (true) { 136 long cleaners = getActiveCleaners(); 137 System.out.println("Waiting for GC to release stub HttpClient;" 138 + " active cache cleaners: " + cleaners); 139 System.gc(); 140 Reference<?> ref = queue.remove(1000); 141 if (ref == weak) { 142 System.out.println("Stub HttpClient GC'ed"); 143 break; 144 } 145 } 146 while (getActiveCleaners() > 0) { 147 System.out.println("Waiting for CacheCleaner to stop"); 148 Thread.sleep(1000); 149 } 150 System.out.println("Active CacheCleaners: " 151 + getActiveCleaners()); 152 153 if (getActiveCleaners() > 0) { 154 throw new RuntimeException("Too many CacheCleaner active: " 155 + getActiveCleaners()); 156 } 157 } 158 static <T> T error() { 159 throw new InternalError("Should not reach here: wrong test assumptions!"); 160 } 161 162 // Emulates an HttpConnection that has a strong reference to its HttpClient. 163 static class HttpConnectionStub extends HttpConnection { 164 165 public HttpConnectionStub(HttpClient client, 166 InetSocketAddress address, 167 InetSocketAddress proxy, 168 boolean secured) { 169 super(address, null); 170 this.key = ConnectionPool.cacheKey(address, proxy); 171 this.address = address; 172 this.proxy = proxy; 173 this.secured = secured; 174 this.client = client; 175 } 176 177 InetSocketAddress proxy; 178 InetSocketAddress address; 179 boolean secured; 180 ConnectionPool.CacheKey key; 181 HttpClient client; 182 183 // All these return something 184 @Override boolean connected() {return true;} 185 @Override boolean isSecure() {return secured;} 186 @Override boolean isProxied() {return proxy!=null;} 187 @Override ConnectionPool.CacheKey cacheKey() {return key;} 188 @Override public void close() {} 189 @Override void shutdownInput() throws IOException {} 190 @Override void shutdownOutput() throws IOException {} 191 public String toString() { 192 return "HttpConnectionStub: " + address + " proxy: " + proxy; 193 } 194 195 // All these throw errors 196 @Override 197 public void connect() throws IOException, InterruptedException {error();} 198 @Override public CompletableFuture<Void> connectAsync() {return error();} 199 @Override SocketChannel channel() {return error();} 200 @Override void flushAsync() throws IOException {error();} 201 @Override 202 protected ByteBuffer readImpl() throws IOException {return error();} 203 @Override CompletableFuture<Void> whenReceivingResponse() {return error();} 204 @Override 205 long write(ByteBuffer[] buffers, int start, int number) throws IOException { 206 throw (Error)error(); 207 } 208 @Override 209 long write(ByteBuffer buffer) throws IOException {throw (Error)error();} 210 @Override 211 void writeAsync(ByteBufferReference[] buffers) throws IOException { 212 error(); 213 } 214 @Override 215 void writeAsyncUnordered(ByteBufferReference[] buffers) 216 throws IOException { 217 error(); 218 } 219 } 220 // Emulates an HttpClient that has a strong reference to its connection pool. 221 static class HttpClientStub extends HttpClient { 222 public HttpClientStub(ConnectionPool pool) { 223 this.pool = pool; 224 } 225 final ConnectionPool pool; 226 @Override public Optional<CookieManager> cookieManager() {return error();} 227 @Override public HttpClient.Redirect followRedirects() {return error();} 228 @Override public Optional<ProxySelector> proxy() {return error();} 229 @Override public SSLContext sslContext() {return error();} 230 @Override public Optional<SSLParameters> sslParameters() {return error();} 231 @Override public Optional<Authenticator> authenticator() {return error();} 232 @Override public HttpClient.Version version() {return HttpClient.Version.HTTP_1_1;} 233 @Override public Executor executor() {return error();} 234 @Override 235 public <T> HttpResponse<T> send(HttpRequest req, 236 HttpResponse.BodyHandler<T> responseBodyHandler) 237 throws IOException, InterruptedException { 238 return error(); 239 } 240 @Override 241 public <T> CompletableFuture<HttpResponse<T>> sendAsync(HttpRequest req, 242 HttpResponse.BodyHandler<T> responseBodyHandler) { 243 return error(); 244 } 245 @Override 246 public <U, T> CompletableFuture<U> sendAsync(HttpRequest req, 247 HttpResponse.MultiProcessor<U, T> multiProcessor) { 248 return error(); 249 } 250 } 251 252 }