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
24package jdk.incubator.http;
25
26import java.io.IOException;
27import java.lang.management.ManagementFactory;
28import java.lang.ref.Reference;
29import java.lang.ref.ReferenceQueue;
30import java.lang.ref.WeakReference;
31import java.net.Authenticator;
32import java.net.CookieManager;
33import java.net.InetSocketAddress;
34import java.net.ProxySelector;
35import java.nio.ByteBuffer;
36import java.nio.channels.SocketChannel;
37import java.util.Optional;
38import java.util.concurrent.CompletableFuture;
39import java.util.concurrent.Executor;
40import javax.net.ssl.SSLContext;
41import javax.net.ssl.SSLParameters;
42import 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 */
51public 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}
253