|
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 } |