/* * Copyright (c) 2017, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package jdk.incubator.http; import java.io.IOException; import java.lang.management.ManagementFactory; import java.net.Authenticator; import java.net.CookieHandler; import java.net.InetSocketAddress; import java.net.ProxySelector; import java.nio.ByteBuffer; import java.nio.channels.SocketChannel; import java.util.List; import java.util.Optional; import java.util.Random; import java.util.concurrent.CompletableFuture; import java.util.concurrent.Executor; import java.util.concurrent.Flow; import java.util.stream.IntStream; import java.time.Instant; import java.time.temporal.ChronoUnit; import javax.net.ssl.SSLContext; import javax.net.ssl.SSLParameters; import jdk.incubator.http.internal.common.FlowTube; /** * @summary Verifies that the ConnectionPool correctly handle * connection deadlines and purges the right connections * from the cache. * @bug 8187044 8187111 * @author danielfuchs */ public class ConnectionPoolTest { static long getActiveCleaners() throws ClassNotFoundException { // ConnectionPool.ACTIVE_CLEANER_COUNTER.get() // ConnectionPoolTest.class.getModule().addReads( // Class.forName("java.lang.management.ManagementFactory").getModule()); return java.util.stream.Stream.of(ManagementFactory.getThreadMXBean() .dumpAllThreads(false, false)) .filter(t -> t.getThreadName().startsWith("HTTP-Cache-cleaner")) .count(); } public static void main(String[] args) throws Exception { testCacheCleaners(); } public static void testCacheCleaners() throws Exception { ConnectionPool pool = new ConnectionPool(666); HttpClient client = new HttpClientStub(pool); InetSocketAddress proxy = InetSocketAddress.createUnresolved("bar", 80); System.out.println("Adding 10 connections to pool"); Random random = new Random(); final int count = 20; Instant now = Instant.now().truncatedTo(ChronoUnit.SECONDS); int[] keepAlives = new int[count]; HttpConnectionStub[] connections = new HttpConnectionStub[count]; long purge = pool.purgeExpiredConnectionsAndReturnNextDeadline(now); long expected = 0; if (purge != expected) { throw new RuntimeException("Bad purge delay: " + purge + ", expected " + expected); } expected = Long.MAX_VALUE; for (int i=0; i 0) { throw new RuntimeException("expected " + min + " got " + purge/1000); } long opened = java.util.stream.Stream.of(connections) .filter(HttpConnectionStub::connected).count(); if (opened != count) { throw new RuntimeException("Opened: expected " + count + " got " + opened); } purge = mean * 1000; System.out.println("start purging at " + purge + " ms"); Instant next = now; do { System.out.println("next purge is in " + purge + " ms"); next = next.plus(purge, ChronoUnit.MILLIS); purge = pool.purgeExpiredConnectionsAndReturnNextDeadline(next); long k = now.until(next, ChronoUnit.SECONDS); System.out.println("now is " + k + "s from start"); for (int i=0; i T error() { throw new InternalError("Should not reach here: wrong test assumptions!"); } static class FlowTubeStub implements FlowTube { final HttpConnectionStub conn; FlowTubeStub(HttpConnectionStub conn) { this.conn = conn; } @Override public void onSubscribe(Flow.Subscription subscription) { } @Override public void onError(Throwable error) { error(); } @Override public void onComplete() { error(); } @Override public void onNext(List item) { error();} @Override public void subscribe(Flow.Subscriber> subscriber) { } @Override public boolean isFinished() { return conn.closed; } } // Emulates an HttpConnection that has a strong reference to its HttpClient. static class HttpConnectionStub extends HttpConnection { public HttpConnectionStub(HttpClient client, InetSocketAddress address, InetSocketAddress proxy, boolean secured) { super(address, null); this.key = ConnectionPool.cacheKey(address, proxy); this.address = address; this.proxy = proxy; this.secured = secured; this.client = client; this.flow = new FlowTubeStub(this); } final InetSocketAddress proxy; final InetSocketAddress address; final boolean secured; final ConnectionPool.CacheKey key; final HttpClient client; final FlowTubeStub flow; volatile boolean closed; // All these return something @Override boolean connected() {return !closed;} @Override boolean isSecure() {return secured;} @Override boolean isProxied() {return proxy!=null;} @Override ConnectionPool.CacheKey cacheKey() {return key;} @Override void shutdownInput() throws IOException {} @Override void shutdownOutput() throws IOException {} @Override public void close() { closed=true; System.out.println("closed: " + this); } @Override public String toString() { return "HttpConnectionStub: " + address + " proxy: " + proxy; } // All these throw errors @Override public HttpPublisher publisher() {return error();} @Override public CompletableFuture connectAsync() {return error();} @Override SocketChannel channel() {return error();} @Override HttpConnection.DetachedConnectionChannel detachChannel() { return error(); } @Override FlowTube getConnectionFlow() {return flow;} } // Emulates an HttpClient that has a strong reference to its connection pool. static class HttpClientStub extends HttpClient { public HttpClientStub(ConnectionPool pool) { this.pool = pool; } final ConnectionPool pool; @Override public Optional cookieHandler() {return error();} @Override public HttpClient.Redirect followRedirects() {return error();} @Override public Optional proxy() {return error();} @Override public SSLContext sslContext() {return error();} @Override public SSLParameters sslParameters() {return error();} @Override public Optional authenticator() {return error();} @Override public HttpClient.Version version() {return HttpClient.Version.HTTP_1_1;} @Override public Optional executor() {return error();} @Override public HttpResponse send(HttpRequest req, HttpResponse.BodyHandler responseBodyHandler) throws IOException, InterruptedException { return error(); } @Override public CompletableFuture> sendAsync(HttpRequest req, HttpResponse.BodyHandler responseBodyHandler) { return error(); } @Override public CompletableFuture sendAsync(HttpRequest req, HttpResponse.MultiSubscriber multiSubscriber) { return error(); } } }