@@ -194,10 +194,10 @@ void requestTimeoutDuringTlsHandshakeDoesNotLeakThePerHostPermit() throws Except
194194 .setSslEngineFactory (createSslEngineFactory (new AtomicBoolean (true )))
195195 .build ();
196196
197- try (BlackHoleServer server = new BlackHoleServer ();
197+ try (BlackHoleServer blackHole = new BlackHoleServer ();
198198 AsyncHttpClient client = asyncHttpClient (cfg )) {
199199 ExecutionException e = assertThrows (ExecutionException .class ,
200- () -> client .prepareGet (server .url ()).execute ().get (30 , TimeUnit .SECONDS ));
200+ () -> client .prepareGet (blackHole .url ()).execute ().get (30 , TimeUnit .SECONDS ));
201201 assertInstanceOf (TimeoutException .class , e .getCause (),
202202 "sanity: the request timeout must win over the handshake timeout" );
203203
@@ -225,13 +225,13 @@ void requestTimeoutClosesTheConnectingSocketWithoutWaitingForHandshakeTimeout()
225225 .setSslEngineFactory (createSslEngineFactory (new AtomicBoolean (true )))
226226 .build ();
227227
228- try (BlackHoleServer server = new BlackHoleServer ();
228+ try (BlackHoleServer blackHole = new BlackHoleServer ();
229229 AsyncHttpClient client = asyncHttpClient (cfg )) {
230230 ExecutionException e = assertThrows (ExecutionException .class ,
231- () -> client .prepareGet (server .url ()).execute ().get (30 , TimeUnit .SECONDS ));
231+ () -> client .prepareGet (blackHole .url ()).execute ().get (30 , TimeUnit .SECONDS ));
232232 assertInstanceOf (TimeoutException .class , e .getCause ());
233233
234- Socket peer = server .awaitFirstConnection (10 , TimeUnit .SECONDS );
234+ Socket peer = blackHole .awaitFirstConnection (10 , TimeUnit .SECONDS );
235235 assertNotNull (peer , "sanity: the client established the TCP connection" );
236236 // Well inside handshakeTimeout: if the abort could not close the connecting channel, draining
237237 // this socket blocks until the read times out instead of reaching EOF.
@@ -244,6 +244,8 @@ void requestTimeoutClosesTheConnectingSocketWithoutWaitingForHandshakeTimeout()
244244 } catch (SocketTimeoutException timeout ) {
245245 fail ("issue #2189: the request timeout must close the connecting socket, not leave it "
246246 + "until handshakeTimeout" );
247+ } catch (IOException reset ) {
248+ // a reset rather than a clean FIN still means the client closed in time
247249 }
248250
249251 assertTrue (probe .get ().released .await (10 , TimeUnit .SECONDS ), "the permit must come back with it" );
@@ -286,13 +288,18 @@ public void handle(String target, org.eclipse.jetty.server.Request baseRequest,
286288 Future <Response > inFlight = client .prepareGet (server .getHttpUrl () + "/foo" ).execute ();
287289 assertTrue (started .await (20 , TimeUnit .SECONDS ), "sanity: the first request reached the server" );
288290
289- ExecutionException refused = assertThrows (ExecutionException .class ,
290- () -> client .prepareGet (server .getHttpUrl () + "/foo" ).execute ().get (20 , TimeUnit .SECONDS ),
291- "a second connection must not be admitted while the first still holds the only permit" );
292- assertInstanceOf (TooManyConnectionsPerHostException .class , refused .getCause ());
293-
294- release .countDown ();
291+ try {
292+ ExecutionException refused = assertThrows (ExecutionException .class ,
293+ () -> client .prepareGet (server .getHttpUrl () + "/foo" ).execute ().get (20 , TimeUnit .SECONDS ),
294+ "a second connection must not be admitted while the first still holds the only permit" );
295+ assertInstanceOf (TooManyConnectionsPerHostException .class , refused .getCause ());
296+ } finally {
297+ release .countDown ();
298+ }
295299 assertEquals (200 , inFlight .get (30 , TimeUnit .SECONDS ).getStatusCode ());
300+
301+ CountingSemaphore semaphore = probe .get ();
302+ assertEquals (1 , semaphore .acquires .get (), "only the served connection ever took a permit" );
296303 }
297304 }
298305
@@ -334,13 +341,18 @@ private static final class BlackHoleServer implements Closeable {
334341 private final List <Socket > accepted = Collections .synchronizedList (new ArrayList <>());
335342 private final BlockingQueue <Socket > firstAccepted = new ArrayBlockingQueue <>(1 );
336343 private final Thread thread ;
344+ private volatile boolean closed ;
337345
338346 BlackHoleServer () throws IOException {
339347 serverSocket = new ServerSocket (0 , 0 , InetAddress .getLoopbackAddress ());
340348 thread = new Thread (() -> {
341349 try {
342- while (!Thread . currentThread (). isInterrupted () ) {
350+ while (!closed ) {
343351 Socket socket = serverSocket .accept ();
352+ if (closed ) {
353+ socket .close ();
354+ return ;
355+ }
344356 accepted .add (socket );
345357 firstAccepted .offer (socket );
346358 }
@@ -352,8 +364,16 @@ private static final class BlackHoleServer implements Closeable {
352364 thread .start ();
353365 }
354366
367+ /**
368+ * Brackets an IPv6 literal, which the loopback address is wherever java.net.preferIPv6Addresses is
369+ * set: an unbracketed "https://::1:443/foo" does not parse as a URI at all.
370+ */
355371 String url () {
356- return "https://" + serverSocket .getInetAddress ().getHostAddress () + ':' + serverSocket .getLocalPort () + "/foo" ;
372+ String host = serverSocket .getInetAddress ().getHostAddress ();
373+ if (host .indexOf (':' ) >= 0 ) {
374+ host = '[' + host + ']' ;
375+ }
376+ return "https://" + host + ':' + serverSocket .getLocalPort () + "/foo" ;
357377 }
358378
359379 Socket awaitFirstConnection (long timeout , TimeUnit unit ) throws InterruptedException {
@@ -362,12 +382,18 @@ Socket awaitFirstConnection(long timeout, TimeUnit unit) throws InterruptedExcep
362382
363383 @ Override
364384 public void close () {
385+ closed = true ;
365386 try {
366387 serverSocket .close ();
367388 } catch (IOException ignored ) {
368389 // best effort test cleanup
369390 }
370- thread .interrupt ();
391+ try {
392+ // join before draining, so a connection accepted during close() cannot be added afterwards
393+ thread .join (TimeUnit .SECONDS .toMillis (5 ));
394+ } catch (InterruptedException e ) {
395+ Thread .currentThread ().interrupt ();
396+ }
371397 synchronized (accepted ) {
372398 for (Socket socket : accepted ) {
373399 try {
0 commit comments