test/jdk/java/net/httpclient/whitebox/java.net.http/jdk/internal/net/http/ConnectionPoolTest.java
branchhttp-client-branch
changeset 56092 fd85b2bf2b0d
parent 56089 42208b2f224e
child 56335 7e56c39fa1fa
equal deleted inserted replaced
56091:aedd6133e7a0 56092:fd85b2bf2b0d
       
     1 /*
       
     2  * Copyright (c) 2017, 2018, 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.internal.net.http;
       
    25 
       
    26 import java.io.IOException;
       
    27 import java.lang.management.ManagementFactory;
       
    28 import java.net.Authenticator;
       
    29 import java.net.CookieHandler;
       
    30 import java.net.InetSocketAddress;
       
    31 import java.net.ProxySelector;
       
    32 import java.nio.ByteBuffer;
       
    33 import java.nio.channels.SocketChannel;
       
    34 import java.util.List;
       
    35 import java.util.Optional;
       
    36 import java.util.Random;
       
    37 import java.util.concurrent.CompletableFuture;
       
    38 import java.util.concurrent.Executor;
       
    39 import java.util.concurrent.Flow;
       
    40 import java.util.stream.IntStream;
       
    41 import java.time.Instant;
       
    42 import java.time.temporal.ChronoUnit;
       
    43 import javax.net.ssl.SSLContext;
       
    44 import javax.net.ssl.SSLParameters;
       
    45 import java.net.http.HttpClient;
       
    46 import java.net.http.HttpRequest;
       
    47 import java.net.http.HttpResponse;
       
    48 import jdk.internal.net.http.common.FlowTube;
       
    49 
       
    50 /**
       
    51  * @summary Verifies that the ConnectionPool correctly handle
       
    52  *          connection deadlines and purges the right connections
       
    53  *          from the cache.
       
    54  * @bug 8187044 8187111
       
    55  * @author danielfuchs
       
    56  */
       
    57 public class ConnectionPoolTest {
       
    58 
       
    59     static long getActiveCleaners() throws ClassNotFoundException {
       
    60         // ConnectionPool.ACTIVE_CLEANER_COUNTER.get()
       
    61         // ConnectionPoolTest.class.getModule().addReads(
       
    62         //      Class.forName("java.lang.management.ManagementFactory").getModule());
       
    63         return java.util.stream.Stream.of(ManagementFactory.getThreadMXBean()
       
    64                 .dumpAllThreads(false, false))
       
    65               .filter(t -> t.getThreadName().startsWith("HTTP-Cache-cleaner"))
       
    66               .count();
       
    67     }
       
    68 
       
    69     public static void main(String[] args) throws Exception {
       
    70         testCacheCleaners();
       
    71     }
       
    72 
       
    73     public static void testCacheCleaners() throws Exception {
       
    74         ConnectionPool pool = new ConnectionPool(666);
       
    75         HttpClient client = new HttpClientStub(pool);
       
    76         InetSocketAddress proxy = InetSocketAddress.createUnresolved("bar", 80);
       
    77         System.out.println("Adding 10 connections to pool");
       
    78         Random random = new Random();
       
    79 
       
    80         final int count = 20;
       
    81         Instant now = Instant.now().truncatedTo(ChronoUnit.SECONDS);
       
    82         int[] keepAlives = new int[count];
       
    83         HttpConnectionStub[] connections = new HttpConnectionStub[count];
       
    84         long purge = pool.purgeExpiredConnectionsAndReturnNextDeadline(now);
       
    85         long expected = 0;
       
    86         if (purge != expected) {
       
    87             throw new RuntimeException("Bad purge delay: " + purge
       
    88                                         + ", expected " + expected);
       
    89         }
       
    90         expected = Long.MAX_VALUE;
       
    91         for (int i=0; i<count; i++) {
       
    92             InetSocketAddress addr = InetSocketAddress.createUnresolved("foo"+i, 80);
       
    93             keepAlives[i] = random.nextInt(10) * 10  + 10;
       
    94             connections[i] = new HttpConnectionStub(client, addr, proxy, true);
       
    95             System.out.println("Adding connection: " + now
       
    96                                 + " keepAlive: " + keepAlives[i]
       
    97                                 + " /" + connections[i]);
       
    98             pool.returnToPool(connections[i], now, keepAlives[i]);
       
    99             expected = Math.min(expected, keepAlives[i] * 1000);
       
   100             purge = pool.purgeExpiredConnectionsAndReturnNextDeadline(now);
       
   101             if (purge != expected) {
       
   102                 throw new RuntimeException("Bad purge delay: " + purge
       
   103                                         + ", expected " + expected);
       
   104             }
       
   105         }
       
   106         int min = IntStream.of(keepAlives).min().getAsInt();
       
   107         int max = IntStream.of(keepAlives).max().getAsInt();
       
   108         int mean = (min + max)/2;
       
   109         System.out.println("min=" + min + ", max=" + max + ", mean=" + mean);
       
   110         purge = pool.purgeExpiredConnectionsAndReturnNextDeadline(now);
       
   111         System.out.println("first purge would be in " + purge + " ms");
       
   112         if (Math.abs(purge/1000 - min) > 0) {
       
   113             throw new RuntimeException("expected " + min + " got " + purge/1000);
       
   114         }
       
   115         long opened = java.util.stream.Stream.of(connections)
       
   116                      .filter(HttpConnectionStub::connected).count();
       
   117         if (opened != count) {
       
   118             throw new RuntimeException("Opened: expected "
       
   119                                        + count + " got " + opened);
       
   120         }
       
   121         purge = mean * 1000;
       
   122         System.out.println("start purging at " + purge + " ms");
       
   123         Instant next = now;
       
   124         do {
       
   125            System.out.println("next purge is in " + purge + " ms");
       
   126            next = next.plus(purge, ChronoUnit.MILLIS);
       
   127            purge = pool.purgeExpiredConnectionsAndReturnNextDeadline(next);
       
   128            long k = now.until(next, ChronoUnit.SECONDS);
       
   129            System.out.println("now is " + k + "s from start");
       
   130            for (int i=0; i<count; i++) {
       
   131                if (connections[i].connected() != (k < keepAlives[i])) {
       
   132                    throw new RuntimeException("Bad connection state for "
       
   133                              + i
       
   134                              + "\n\t connected=" + connections[i].connected()
       
   135                              + "\n\t keepAlive=" + keepAlives[i]
       
   136                              + "\n\t elapsed=" + k);
       
   137                }
       
   138            }
       
   139         } while (purge > 0);
       
   140         opened = java.util.stream.Stream.of(connections)
       
   141                      .filter(HttpConnectionStub::connected).count();
       
   142         if (opened != 0) {
       
   143            throw new RuntimeException("Closed: expected "
       
   144                                        + count + " got "
       
   145                                        + (count-opened));
       
   146         }
       
   147     }
       
   148 
       
   149     static <T> T error() {
       
   150         throw new InternalError("Should not reach here: wrong test assumptions!");
       
   151     }
       
   152 
       
   153     static class FlowTubeStub implements FlowTube {
       
   154         final HttpConnectionStub conn;
       
   155         FlowTubeStub(HttpConnectionStub conn) { this.conn = conn; }
       
   156         @Override
       
   157         public void onSubscribe(Flow.Subscription subscription) { }
       
   158         @Override public void onError(Throwable error) { error(); }
       
   159         @Override public void onComplete() { error(); }
       
   160         @Override public void onNext(List<ByteBuffer> item) { error();}
       
   161         @Override
       
   162         public void subscribe(Flow.Subscriber<? super List<ByteBuffer>> subscriber) {
       
   163         }
       
   164         @Override public boolean isFinished() { return conn.closed; }
       
   165     }
       
   166 
       
   167     // Emulates an HttpConnection that has a strong reference to its HttpClient.
       
   168     static class HttpConnectionStub extends HttpConnection {
       
   169 
       
   170         public HttpConnectionStub(HttpClient client,
       
   171                 InetSocketAddress address,
       
   172                 InetSocketAddress proxy,
       
   173                 boolean secured) {
       
   174             super(address, null);
       
   175             this.key = ConnectionPool.cacheKey(address, proxy);
       
   176             this.address = address;
       
   177             this.proxy = proxy;
       
   178             this.secured = secured;
       
   179             this.client = client;
       
   180             this.flow = new FlowTubeStub(this);
       
   181         }
       
   182 
       
   183         final InetSocketAddress proxy;
       
   184         final InetSocketAddress address;
       
   185         final boolean secured;
       
   186         final ConnectionPool.CacheKey key;
       
   187         final HttpClient client;
       
   188         final FlowTubeStub flow;
       
   189         volatile boolean closed;
       
   190 
       
   191         // All these return something
       
   192         @Override boolean connected() {return !closed;}
       
   193         @Override boolean isSecure() {return secured;}
       
   194         @Override boolean isProxied() {return proxy!=null;}
       
   195         @Override ConnectionPool.CacheKey cacheKey() {return key;}
       
   196         @Override void shutdownInput() throws IOException {}
       
   197         @Override void shutdownOutput() throws IOException {}
       
   198         @Override
       
   199         public void close() {
       
   200             closed=true;
       
   201             System.out.println("closed: " + this);
       
   202         }
       
   203         @Override
       
   204         public String toString() {
       
   205             return "HttpConnectionStub: " + address + " proxy: " + proxy;
       
   206         }
       
   207 
       
   208         // All these throw errors
       
   209         @Override public HttpPublisher publisher() {return error();}
       
   210         @Override public CompletableFuture<Void> connectAsync() {return error();}
       
   211         @Override SocketChannel channel() {return error();}
       
   212         @Override
       
   213         HttpConnection.DetachedConnectionChannel detachChannel() {
       
   214             return error();
       
   215         }
       
   216         @Override
       
   217         FlowTube getConnectionFlow() {return flow;}
       
   218     }
       
   219     // Emulates an HttpClient that has a strong reference to its connection pool.
       
   220     static class HttpClientStub extends HttpClient {
       
   221         public HttpClientStub(ConnectionPool pool) {
       
   222             this.pool = pool;
       
   223         }
       
   224         final ConnectionPool pool;
       
   225         @Override public Optional<CookieHandler> cookieHandler() {return error();}
       
   226         @Override public HttpClient.Redirect followRedirects() {return error();}
       
   227         @Override public Optional<ProxySelector> proxy() {return error();}
       
   228         @Override public SSLContext sslContext() {return error();}
       
   229         @Override public SSLParameters sslParameters() {return error();}
       
   230         @Override public Optional<Authenticator> authenticator() {return error();}
       
   231         @Override public HttpClient.Version version() {return HttpClient.Version.HTTP_1_1;}
       
   232         @Override public Optional<Executor> executor() {return error();}
       
   233         @Override
       
   234         public <T> HttpResponse<T> send(HttpRequest req,
       
   235                                         HttpResponse.BodyHandler<T> responseBodyHandler)
       
   236                 throws IOException, InterruptedException {
       
   237             return error();
       
   238         }
       
   239         @Override
       
   240         public <T> CompletableFuture<HttpResponse<T>> sendAsync(HttpRequest req,
       
   241                 HttpResponse.BodyHandler<T> responseBodyHandler) {
       
   242             return error();
       
   243         }
       
   244         @Override
       
   245         public <T> CompletableFuture<HttpResponse<T>> sendAsync(HttpRequest req,
       
   246                 HttpResponse.BodyHandler<T> bodyHandler,
       
   247                 HttpResponse.PushPromiseHandler<T> multiHandler) {
       
   248             return error();
       
   249         }
       
   250     }
       
   251 
       
   252 }