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 }