11/*
2- * Copyright (c) 2015, 2020 , Oracle and/or its affiliates. All rights reserved.
2+ * Copyright (c) 2015, 2021 , Oracle and/or its affiliates. All rights reserved.
33 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
44 *
55 * This code is free software; you can redistribute it and/or modify it
3232 * @compile ../../../com/sun/net/httpserver/LogFilter.java
3333 * @compile ../../../com/sun/net/httpserver/EchoHandler.java
3434 * @compile ../../../com/sun/net/httpserver/FileServerHandler.java
35- * @run main/othervm/timeout=40 -Djdk.httpclient.HttpClient.log=ssl ManyRequests
36- * @run main/othervm/timeout=40 -Dtest.insertDelay=true ManyRequests
37- * @run main/othervm/timeout=40 -Dtest.chunkSize=64 ManyRequests
38- * @run main/othervm/timeout=40 -Dtest.insertDelay=true -Dtest.chunkSize=64 ManyRequests
35+ * @run main/othervm/timeout=40 -Djdk.httpclient.HttpClient.log=ssl,channel ManyRequests
36+ * @run main/othervm/timeout=40 -Djdk.httpclient.HttpClient.log=channel - Dtest.insertDelay=true ManyRequests
37+ * @run main/othervm/timeout=40 -Djdk.httpclient.HttpClient.log=channel - Dtest.chunkSize=64 ManyRequests
38+ * @run main/othervm/timeout=40 -Djdk.httpclient.HttpClient.log=channel - Dtest.insertDelay=true -Dtest.chunkSize=64 ManyRequests
3939 * @summary Send a large number of requests asynchronously
4040 */
41- // * @run main/othervm/timeout=40 -Djdk.httpclient.HttpClient.log=ssl ManyRequests
41+ // * @run main/othervm/timeout=40 -Djdk.httpclient.HttpClient.log=ssl,channel ManyRequests
4242
4343import com .sun .net .httpserver .HttpsConfigurator ;
4444import com .sun .net .httpserver .HttpsParameters ;
4747import java .io .IOException ;
4848import java .io .InputStream ;
4949import java .io .OutputStream ;
50+ import java .net .ConnectException ;
5051import java .net .InetAddress ;
5152import java .net .InetSocketAddress ;
5253import java .net .URI ;
5354import java .net .http .HttpClient ;
5455import java .net .http .HttpClient .Builder ;
5556import java .net .http .HttpRequest ;
5657import java .net .http .HttpRequest .BodyPublishers ;
58+ import java .net .http .HttpResponse ;
5759import java .net .http .HttpResponse .BodyHandlers ;
5860import java .time .Duration ;
5961import java .util .Arrays ;
6062import java .util .Formatter ;
6163import java .util .HashMap ;
6264import java .util .LinkedList ;
65+ import java .util .Map ;
6366import java .util .Random ;
67+ import java .util .concurrent .CompletionException ;
68+ import java .util .concurrent .CompletionStage ;
69+ import java .util .concurrent .ConcurrentHashMap ;
6470import java .util .concurrent .ExecutorService ;
6571import java .util .concurrent .Executors ;
6672import java .util .concurrent .ThreadFactory ;
6773import java .util .concurrent .atomic .AtomicInteger ;
6874import java .util .logging .Logger ;
6975import java .util .logging .Level ;
7076import java .util .concurrent .CompletableFuture ;
77+ import java .util .stream .Stream ;
7178import javax .net .ssl .SSLContext ;
79+
80+ import jdk .test .lib .Platform ;
81+ import jdk .test .lib .RandomFactory ;
7282import jdk .test .lib .net .SimpleSSLContext ;
83+ import jdk .test .lib .net .URIBuilder ;
7384
7485public class ManyRequests {
7586
76- volatile static int counter = 0 ;
87+ static final int MAX_COUNT = 20 ;
88+ static final int MAX_LIMIT = 40 ;
89+ static final AtomicInteger COUNT = new AtomicInteger ();
90+ static final AtomicInteger LIMIT = new AtomicInteger (MAX_LIMIT );
91+ static final Random RANDOM = RandomFactory .getRandom ();
7792
7893 public static void main (String [] args ) throws Exception {
7994 Logger logger = Logger .getLogger ("com.sun.net.httpserver" );
8095 logger .setLevel (Level .ALL );
8196 logger .info ("TEST" );
97+ Stream .of (Logger .getLogger ("" ).getHandlers ()).forEach ((h ) -> h .setLevel (Level .ALL ));
8298 System .out .println ("Sending " + REQUESTS
8399 + " requests; delay=" + INSERT_DELAY
84100 + ", chunks=" + CHUNK_SIZE
@@ -106,14 +122,14 @@ public static void main(String[] args) throws Exception {
106122 }
107123
108124 //static final int REQUESTS = 1000;
109- static final int REQUESTS = 20 ;
125+ static final int REQUESTS = MAX_COUNT ;
110126 static final boolean INSERT_DELAY = Boolean .getBoolean ("test.insertDelay" );
111127 static final int CHUNK_SIZE = Math .max (0 ,
112128 Integer .parseInt (System .getProperty ("test.chunkSize" , "0" )));
113129 static final boolean XFIXED = Boolean .getBoolean ("test.XFixed" );
114130
115131 static class TestEchoHandler extends EchoHandler {
116- final Random rand = jdk . test . lib . RandomFactory . getRandom () ;
132+ final Random rand = RANDOM ;
117133 @ Override
118134 public void handle (HttpExchange e ) throws IOException {
119135 System .out .println ("Server: received " + e .getRequestURI ());
@@ -139,60 +155,126 @@ protected void close(HttpExchange t, InputStream is) throws IOException {
139155 }
140156 }
141157
158+ static String now (long start ) {
159+ long elapsed = System .nanoTime () - start ;
160+ long ms = elapsed / 1000_000L ;
161+ long s = ms / 1000L ;
162+ if (s == 0 ) return ms + "ms: " ;
163+ return s + "s, " + (ms - s * 1000L ) + "ms: " ;
164+ }
165+
166+ static String failure (Throwable t ) {
167+ String s = "\n \t failed: " + t ;
168+ for (t = t .getCause (); t != null ; t = t .getCause ()) {
169+ s = s + "\n \t \t Caused by: " + t ;
170+ }
171+ return s ;
172+ }
173+
142174 static void test (HttpsServer server , HttpClient client ) throws Exception {
143175 int port = server .getAddress ().getPort ();
144- URI baseURI = new URI ("https://localhost:" + port + "/foo/x" );
176+
177+ URI baseURI = URIBuilder .newBuilder ()
178+ .scheme ("https" )
179+ .host (InetAddress .getLoopbackAddress ().getHostName ())
180+ .port (port )
181+ .path ("/foo/x" ).build ();
145182 server .createContext ("/foo" , new TestEchoHandler ());
146183 server .start ();
147184
148- RequestLimiter limiter = new RequestLimiter (40 );
149- Random rand = new Random ();
150- CompletableFuture <?>[] results = new CompletableFuture <?>[REQUESTS ];
151- HashMap <HttpRequest ,byte []> bodies = new HashMap <>();
152-
153- for (int i =0 ; i <REQUESTS ; i ++) {
154- byte [] buf = new byte [(i +1 )*CHUNK_SIZE +i +1 ]; // different size bodies
155- rand .nextBytes (buf );
156- URI uri = new URI (baseURI .toString () + String .valueOf (i +1 ));
157- HttpRequest r = HttpRequest .newBuilder (uri )
158- .header ("XFixed" , "true" )
159- .POST (BodyPublishers .ofByteArray (buf ))
160- .build ();
161- bodies .put (r , buf );
162-
163- results [i ] =
164- limiter .whenOkToSend ()
165- .thenCompose ((v ) -> {
166- System .out .println ("Client: sendAsync: " + r .uri ());
167- return client .sendAsync (r , BodyHandlers .ofByteArray ());
168- })
169- .thenCompose ((resp ) -> {
170- limiter .requestComplete ();
171- if (resp .statusCode () != 200 ) {
172- String s = "Expected 200, got: " + resp .statusCode ();
173- System .out .println (s + " from "
174- + resp .request ().uri ().getPath ());
175- return completedWithIOException (s );
176- } else {
177- counter ++;
178- System .out .println ("Result (" + counter + ") from "
179- + resp .request ().uri ().getPath ());
180- }
181- return CompletableFuture .completedStage (resp .body ())
182- .thenApply ((b ) -> new Pair <>(resp , b ));
183- })
184- .thenAccept ((pair ) -> {
185- HttpRequest request = pair .t .request ();
186- byte [] requestBody = bodies .get (request );
187- check (Arrays .equals (requestBody , pair .u ),
188- "bodies not equal:[" + bytesToHexString (requestBody )
189- + "] [" + bytesToHexString (pair .u ) + "]" );
190-
191- });
192- }
185+ // This loop implements a retry mechanism to work around an issue
186+ // on some systems (observed on Windows 10) that seem to be trying to
187+ // throttle the number of connections that can be made concurrently by
188+ // rejecting connection attempts.
189+ // On the first iteration of this loop, we will attempt 20 concurrent
190+ // requests. If this fails with ConnectException, we will retry the
191+ // 20 requests, but limiting the concurrency to 10 (LIMIT <- 10).
192+ // If this fails again, the test will fail.
193+ boolean done = false ;
194+ LOOP : do {
195+ RequestLimiter limiter = new RequestLimiter (LIMIT .get ());
196+ Random rand = RANDOM ;
197+ CompletableFuture <?>[] results = new CompletableFuture <?>[REQUESTS ];
198+ Map <HttpRequest ,byte []> bodies = new ConcurrentHashMap <>();
199+
200+ long start = System .nanoTime ();
201+
202+ for (int i = 0 ; i < REQUESTS ; i ++) {
203+ byte [] buf = new byte [(i + 1 ) * CHUNK_SIZE + i + 1 ]; // different size bodies
204+ rand .nextBytes (buf );
205+ URI uri = new URI (baseURI .toString () + String .valueOf (i + 1 ));
206+ HttpRequest r = HttpRequest .newBuilder (uri )
207+ .header ("XFixed" , "true" )
208+ .POST (BodyPublishers .ofByteArray (buf ))
209+ .build ();
210+ bodies .put (r , buf );
211+
212+ results [i ] =
213+ limiter .whenOkToSend ()
214+ .thenCompose ((v ) -> {
215+ System .out .println ("Client: sendAsync: " + r .uri ());
216+ return client .sendAsync (r , BodyHandlers .ofByteArray ());
217+ })
218+ .handle ((resp , t ) -> {
219+ limiter .requestComplete ();
220+ CompletionStage <Pair <HttpResponse <byte []>, byte []>> res ;
221+ String now = now (start );
222+ if (t == null ) {
223+ if (resp .statusCode () != 200 ) {
224+ String s = "Expected 200, got: " + resp .statusCode ();
225+ System .out .println (now + s + " from "
226+ + resp .request ().uri ().getPath ());
227+ res = completedWithIOException (s );
228+ return res ;
229+ } else {
230+ int counter = COUNT .incrementAndGet ();
231+ System .out .println (now + "Result (" + counter + ") from "
232+ + resp .request ().uri ().getPath ());
233+ }
234+ res = CompletableFuture .completedStage (resp .body ())
235+ .thenApply ((b ) -> new Pair <>(resp , b ));
236+ return res ;
237+ } else {
238+ int counter = COUNT .incrementAndGet ();
239+ System .out .println (now + "Result (" + counter + ") from "
240+ + r .uri ().getPath ()
241+ + failure (t ));
242+ res = CompletableFuture .failedFuture (t );
243+ return res ;
244+ }
245+ })
246+ .thenCompose (c -> c )
247+ .thenAccept ((pair ) -> {
248+ HttpRequest request = pair .t .request ();
249+ byte [] requestBody = bodies .get (request );
250+ check (Arrays .equals (requestBody , pair .u ),
251+ "bodies not equal:[" + bytesToHexString (requestBody )
252+ + "] [" + bytesToHexString (pair .u ) + "]" );
253+
254+ });
255+ }
256+
257+ // wait for them all to complete and throw exception in case of err
258+ try {
259+ CompletableFuture .allOf (results ).join ();
260+ done = true ;
261+ } catch (CompletionException e ) {
262+ if (!Platform .isWindows ()) throw e ;
263+ if (LIMIT .get () < REQUESTS ) throw e ;
264+ Throwable cause = e ;
265+ while ((cause = cause .getCause ()) != null ) {
266+ if (cause instanceof ConnectException ) {
267+ // try again, limit concurrency by half
268+ COUNT .set (0 );
269+ LIMIT .set (REQUESTS /2 );
270+ System .out .println ("*** Retrying due to " + cause );
271+ continue LOOP ;
272+ }
273+ }
274+ throw e ;
275+ }
276+ } while (!done );
193277
194- // wait for them all to complete and throw exception in case of error
195- CompletableFuture .allOf (results ).join ();
196278 }
197279
198280 static <T > CompletableFuture <T > completedWithIOException (String message ) {
@@ -213,13 +295,7 @@ static String bytesToHexString(byte[] bytes) {
213295 return sb .toString ();
214296 }
215297
216- static final class Pair <T ,U > {
217- Pair (T t , U u ) {
218- this .t = t ; this .u = u ;
219- }
220- T t ;
221- U u ;
222- }
298+ record Pair <T ,U >(T t , U u ) { }
223299
224300 /**
225301 * A simple limiter for controlling the number of requests to be run in
0 commit comments