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