Skip to content

Commit be44aba

Browse files
committed
Backport d83a07b72cfd4dc42c5d4815262fcba05c653bd5
1 parent 34f76e3 commit be44aba

File tree

4 files changed

+103
-24
lines changed

4 files changed

+103
-24
lines changed

src/java.net.http/share/classes/jdk/internal/net/http/HttpClientImpl.java

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -355,6 +355,7 @@ static void abortPendingRequests(HttpClientImpl client, Throwable reason) {
355355
// nature of the API, we also need to wait until all pending operations
356356
// have completed.
357357
private final WeakReference<HttpClientFacade> facadeRef;
358+
private final WeakReference<HttpClientImpl> implRef;
358359

359360
private final ConcurrentSkipListSet<PlainHttpConnection> openedConnections
360361
= new ConcurrentSkipListSet<>(HttpConnection.COMPARE_BY_ID);
@@ -398,6 +399,7 @@ static void abortPendingRequests(HttpClientImpl client, Throwable reason) {
398399
private final AtomicLong pendingTCPConnectionCount = new AtomicLong();
399400
private final AtomicLong pendingSubscribersCount = new AtomicLong();
400401
private final AtomicBoolean isAlive = new AtomicBoolean();
402+
private final AtomicBoolean isStarted = new AtomicBoolean();
401403

402404
/** A Set of, deadline first, ordered timeout events. */
403405
private final TreeSet<TimeoutEvent> timeouts;
@@ -455,6 +457,7 @@ private HttpClientImpl(HttpClientBuilderImpl builder,
455457
delegatingExecutor = new DelegatingExecutor(this::isSelectorThread, ex,
456458
this::onSubmitFailure);
457459
facadeRef = new WeakReference<>(facadeFactory.createFacade(this));
460+
implRef = new WeakReference<>(this);
458461
client2 = new Http2ClientImpl(this);
459462
cookieHandler = builder.cookieHandler;
460463
connectTimeout = builder.connectTimeout;
@@ -504,7 +507,12 @@ void onSubmitFailure(Runnable command, Throwable failure) {
504507
}
505508

506509
private void start() {
507-
selmgr.start();
510+
try {
511+
selmgr.start();
512+
} catch (Throwable t) {
513+
isStarted.set(true);
514+
throw t;
515+
}
508516
}
509517

510518
// Called from the SelectorManager thread, just before exiting.
@@ -699,7 +707,9 @@ final static class HttpClientTracker implements Tracker {
699707
final AtomicLong connnectionsCount;
700708
final AtomicLong subscribersCount;
701709
final Reference<?> reference;
710+
final Reference<?> implRef;
702711
final AtomicBoolean isAlive;
712+
final AtomicBoolean isStarted;
703713
final String name;
704714
HttpClientTracker(AtomicLong request,
705715
AtomicLong http,
@@ -709,7 +719,9 @@ final static class HttpClientTracker implements Tracker {
709719
AtomicLong conns,
710720
AtomicLong subscribers,
711721
Reference<?> ref,
722+
Reference<?> implRef,
712723
AtomicBoolean isAlive,
724+
AtomicBoolean isStarted,
713725
String name) {
714726
this.requestCount = request;
715727
this.httpCount = http;
@@ -719,7 +731,9 @@ final static class HttpClientTracker implements Tracker {
719731
this.connnectionsCount = conns;
720732
this.subscribersCount = subscribers;
721733
this.reference = ref;
734+
this.implRef = implRef;
722735
this.isAlive = isAlive;
736+
this.isStarted = isStarted;
723737
this.name = name;
724738
}
725739
@Override
@@ -750,8 +764,12 @@ public long getOutstandingWebSocketOperations() {
750764
public boolean isFacadeReferenced() {
751765
return !reference.refersTo(null);
752766
}
767+
public boolean isImplementationReferenced() {
768+
return !implRef.refersTo(null);
769+
}
770+
// The selector is considered alive if it's not yet started
753771
@Override
754-
public boolean isSelectorAlive() { return isAlive.get(); }
772+
public boolean isSelectorAlive() { return isAlive.get() || !isStarted.get(); }
755773
@Override
756774
public String getName() {
757775
return name;
@@ -768,7 +786,9 @@ public Tracker getOperationsTracker() {
768786
pendingTCPConnectionCount,
769787
pendingSubscribersCount,
770788
facadeRef,
789+
implRef,
771790
isAlive,
791+
isStarted,
772792
dbgTag);
773793
}
774794

@@ -1144,7 +1164,8 @@ public void run() {
11441164
List<Pair<AsyncEvent,IOException>> errorList = new ArrayList<>();
11451165
List<AsyncEvent> readyList = new ArrayList<>();
11461166
List<Runnable> resetList = new ArrayList<>();
1147-
owner.isAlive.set(true);
1167+
owner.isAlive.set(true); // goes back to false when run exits
1168+
owner.isStarted.set(true); // never goes back to false
11481169
try {
11491170
if (Log.channel()) Log.logChannel(getName() + ": starting");
11501171
while (!Thread.currentThread().isInterrupted() && !closed) {

src/java.net.http/share/classes/jdk/internal/net/http/common/OperationTrackers.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,9 @@ public interface Tracker {
6464
// Whether the facade returned to the
6565
// user is still referenced
6666
boolean isFacadeReferenced();
67+
// Whether the implementation of the facade
68+
// is still referenced
69+
boolean isImplementationReferenced();
6770
// whether the Selector Manager thread is still running
6871
boolean isSelectorAlive();
6972
// The name of the object being tracked.

test/jdk/java/net/httpclient/ReferenceTracker.java

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
import java.lang.management.MonitorInfo;
3131
import java.lang.management.ThreadInfo;
3232
import java.net.http.HttpClient;
33+
import java.time.Duration;
3334
import java.util.Arrays;
3435
import java.util.Objects;
3536
import java.util.concurrent.ConcurrentLinkedQueue;
@@ -103,7 +104,7 @@ public AssertionError check(Tracker tracker, long graceDelayMs) {
103104
hasOperations.or(hasSubscribers)
104105
.or(Tracker::isFacadeReferenced)
105106
.or(Tracker::isSelectorAlive),
106-
"outstanding operations or unreleased resources", false);
107+
"outstanding operations or unreleased resources", true);
107108
}
108109

109110
public AssertionError check(long graceDelayMs) {
@@ -210,32 +211,46 @@ public AssertionError check(Tracker tracker,
210211
graceDelayMs = Math.max(graceDelayMs, 100);
211212
long delay = Math.min(graceDelayMs, 10);
212213
var count = delay > 0 ? graceDelayMs / delay : 1;
214+
long waitStart = System.nanoTime();
215+
long waited = 0;
216+
long toWait = Math.min(graceDelayMs, Math.max(delay, 1));
213217
for (int i = 0; i < count; i++) {
214218
if (hasOutstanding.test(tracker)) {
215219
System.gc();
216220
try {
217221
if (i == 0) {
218222
System.out.println("Waiting for HTTP operations to terminate...");
219223
}
220-
Thread.sleep(Math.min(graceDelayMs, Math.max(delay, 1)));
224+
waited += toWait;
225+
Thread.sleep(toWait);
221226
} catch (InterruptedException x) {
222227
// OK
223228
}
224-
} else break;
229+
} else {
230+
System.out.println("No outstanding HTTP operations remaining after "
231+
+ i + "/" + count + " iterations and " + waited + "/" + graceDelayMs
232+
+ " ms, (wait/iteration " + toWait + " ms)");
233+
break;
234+
}
225235
}
236+
long duration = Duration.ofNanos(System.nanoTime() - waitStart).toMillis();
226237
if (hasOutstanding.test(tracker)) {
227238
StringBuilder warnings = diagnose(tracker, new StringBuilder(), hasOutstanding);
228239
if (hasOutstanding.test(tracker)) {
229240
fail = new AssertionError(warnings.toString());
230241
}
231242
} else {
232-
System.out.println("PASSED: No " + description + " found in " + tracker.getName());
243+
System.out.println("PASSED: No " + description + " found in "
244+
+ tracker.getName() + " in " + duration + " ms");
233245
}
234246
if (fail != null) {
235247
if (printThreads && tracker.isSelectorAlive()) {
236-
printThreads("Some selector manager threads are still alive: ", System.out);
237-
printThreads("Some selector manager threads are still alive: ", System.err);
248+
var msg = "Selector manager threads are still alive for " + tracker.getName() + ": ";
249+
printThreads(msg, System.out);
250+
printThreads(msg, System.err);
238251
}
252+
System.out.println("AssertionError: Found some " + description + " in "
253+
+ tracker.getName() + " after " + duration + " ms, waited " + waited + " ms");
239254
}
240255
return fail;
241256
}

test/jdk/java/net/httpclient/SpecialHeadersTest.java

Lines changed: 55 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
* @test
2626
* @summary Verify that some special headers - such as User-Agent
2727
* can be specified by the caller.
28-
* @bug 8203771 8218546
28+
* @bug 8203771 8218546 8297200
2929
* @library /test/lib /test/jdk/java/net/httpclient/lib
3030
* @build jdk.httpclient.test.lib.common.HttpServerAdapters
3131
* jdk.httpclient.test.lib.http2.Http2TestServer
@@ -42,6 +42,7 @@
4242
import com.sun.net.httpserver.HttpServer;
4343
import com.sun.net.httpserver.HttpsConfigurator;
4444
import com.sun.net.httpserver.HttpsServer;
45+
import jdk.internal.net.http.common.OperationTrackers.Tracker;
4546
import jdk.test.lib.net.SimpleSSLContext;
4647
import org.testng.ITestContext;
4748
import org.testng.ITestResult;
@@ -300,13 +301,18 @@ static String userAgent() {
300301
static final Map<String, Function<URI,String>> DEFAULTS = Map.of(
301302
"USER-AGENT", u -> userAgent(), "HOST", u -> u.getRawAuthority());
302303

304+
static void throwIfNotNull(Throwable throwable) throws Exception {
305+
if (throwable instanceof Exception ex) throw ex;
306+
if (throwable instanceof Error e) throw e;
307+
}
308+
303309
@Test(dataProvider = "variants")
304310
void test(String uriString,
305311
String headerNameAndValue,
306312
boolean sameClient)
307313
throws Exception
308314
{
309-
out.println("\n--- Starting ");
315+
out.println("\n--- Starting test " + now());
310316

311317
int index = headerNameAndValue.indexOf(":");
312318
String name = headerNameAndValue.substring(0, index);
@@ -318,10 +324,14 @@ void test(String uriString,
318324
String value = useDefault ? DEFAULTS.get(key).apply(uri) : v;
319325

320326
HttpClient client = null;
327+
Tracker tracker = null;
328+
Throwable thrown = null;
321329
for (int i=0; i< ITERATION_COUNT; i++) {
322330
try {
323-
if (!sameClient || client == null)
331+
if (!sameClient || client == null) {
324332
client = newHttpClient("test", sameClient);
333+
tracker = TRACKER.getTracker(client);
334+
}
325335

326336
HttpRequest.Builder requestBuilder = HttpRequest.newBuilder(uri);
327337
if (!useDefault) {
@@ -361,14 +371,20 @@ void test(String uriString,
361371
assertEquals(resp.headers().allValues("X-" + key).size(), 0);
362372
}
363373
}
374+
} catch (Throwable x) {
375+
thrown = x;
364376
} finally {
365377
if (!sameClient) {
366378
client = null;
367379
System.gc();
368-
var error = TRACKER.check(500);
369-
if (error != null) throw error;
380+
var error = TRACKER.check(tracker, 500);
381+
if (error != null) {
382+
if (thrown != null) error.addSuppressed(thrown);
383+
throw error;
384+
}
370385
}
371386
}
387+
throwIfNotNull(thrown);
372388
}
373389
}
374390

@@ -378,11 +394,12 @@ void testHomeMadeIllegalHeader(String uriString,
378394
boolean sameClient)
379395
throws Exception
380396
{
381-
out.println("\n--- Starting ");
397+
out.println("\n--- Starting testHomeMadeIllegalHeader " + now());
382398
final URI uri = URI.create(uriString);
383399

384400
HttpClient client = newHttpClient("testHomeMadeIllegalHeader", sameClient);
385-
401+
Tracker tracker = TRACKER.getTracker(client);
402+
Throwable thrown = null;
386403
try {
387404
// Test a request which contains an illegal header created
388405
HttpRequest req = new HttpRequest() {
@@ -429,19 +446,29 @@ public HttpHeaders headers() {
429446
} catch (IllegalArgumentException ee) {
430447
out.println("Got IAE as expected");
431448
}
449+
} catch (Throwable x) {
450+
thrown = x;
432451
} finally {
433452
if (!sameClient) {
434453
client = null;
435454
System.gc();
436-
var error = TRACKER.check(500);
437-
if (error != null) throw error;
455+
var error = TRACKER.check(tracker, 500);
456+
if (error != null) {
457+
if (thrown != null) error.addSuppressed(thrown);
458+
throw error;
459+
}
438460
}
439461
}
462+
throwIfNotNull(thrown);
440463
}
441464

465+
466+
442467
@Test(dataProvider = "variants")
443-
void testAsync(String uriString, String headerNameAndValue, boolean sameClient) {
444-
out.println("\n--- Starting ");
468+
void testAsync(String uriString, String headerNameAndValue, boolean sameClient)
469+
throws Exception
470+
{
471+
out.println("\n--- Starting testAsync " + now());
445472
int index = headerNameAndValue.indexOf(":");
446473
String name = headerNameAndValue.substring(0, index);
447474
String v = headerNameAndValue.substring(index+1).trim();
@@ -452,10 +479,14 @@ void testAsync(String uriString, String headerNameAndValue, boolean sameClient)
452479
String value = useDefault ? DEFAULTS.get(key).apply(uri) : v;
453480

454481
HttpClient client = null;
482+
Tracker tracker = null;
483+
Throwable thrown = null;
455484
for (int i=0; i< ITERATION_COUNT; i++) {
456485
try {
457-
if (!sameClient || client == null)
486+
if (!sameClient || client == null) {
458487
client = newHttpClient("testAsync", sameClient);
488+
tracker = TRACKER.getTracker(client);
489+
}
459490

460491
HttpRequest.Builder requestBuilder = HttpRequest.newBuilder(uri);
461492
if (!useDefault) {
@@ -498,14 +529,20 @@ void testAsync(String uriString, String headerNameAndValue, boolean sameClient)
498529
}
499530
})
500531
.join();
532+
} catch (Throwable x) {
533+
thrown = x;
501534
} finally {
502535
if (!sameClient) {
503536
client = null;
504537
System.gc();
505-
var error = TRACKER.check(500);
506-
if (error != null) throw error;
538+
var error = TRACKER.check(tracker, 500);
539+
if (error != null) {
540+
if (thrown != null) error.addSuppressed(thrown);
541+
throw error;
542+
}
507543
}
508544
}
545+
throwIfNotNull(thrown);
509546
}
510547
}
511548

@@ -516,6 +553,7 @@ static String serverAuthority(HttpTestServer server) {
516553

517554
@BeforeTest
518555
public void setup() throws Exception {
556+
out.println("--- Starting setup " + now());
519557
sslContext = new SimpleSSLContext().get();
520558
if (sslContext == null)
521559
throw new AssertionError("Unexpected null sslContext");
@@ -545,13 +583,15 @@ public void setup() throws Exception {
545583

546584
@AfterTest
547585
public void teardown() throws Exception {
586+
out.println("\n--- Teardown " + now());
548587
HttpClient shared = sharedClient;
549588
String sharedClientName =
550589
shared == null ? null : shared.toString();
551590
if (shared != null) TRACKER.track(shared);
552591
shared = sharedClient = null;
553592
Thread.sleep(100);
554-
AssertionError fail = TRACKER.check(500);
593+
AssertionError fail = TRACKER.check(2500);
594+
out.println("--- Stopping servers " + now());
555595
try {
556596
httpTestServer.stop();
557597
httpsTestServer.stop();

0 commit comments

Comments
 (0)