8336655: java/net/httpclient/DigestEchoClient.java IOException: HTTP/1.1 header parser received no bytes

Reviewed-by: jpai
This commit is contained in:
Daniel Fuchs 2024-08-15 15:34:08 +00:00
parent 3859131505
commit 6169613d9f
3 changed files with 52 additions and 11 deletions

View File

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2015, 2023, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2015, 2024, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* This code is free software; you can redistribute it and/or modify it * This code is free software; you can redistribute it and/or modify it
@ -44,6 +44,7 @@ import java.util.stream.Collectors;
import jdk.internal.net.http.common.Deadline; import jdk.internal.net.http.common.Deadline;
import jdk.internal.net.http.common.FlowTube; import jdk.internal.net.http.common.FlowTube;
import jdk.internal.net.http.common.Log;
import jdk.internal.net.http.common.Logger; import jdk.internal.net.http.common.Logger;
import jdk.internal.net.http.common.TimeLine; import jdk.internal.net.http.common.TimeLine;
import jdk.internal.net.http.common.TimeSource; import jdk.internal.net.http.common.TimeSource;
@ -492,13 +493,13 @@ final class ConnectionPool {
// Remove a connection from the pool. // Remove a connection from the pool.
// should only be called while holding the ConnectionPool stateLock. // should only be called while holding the ConnectionPool stateLock.
private void removeFromPool(HttpConnection c) { private boolean removeFromPool(HttpConnection c) {
assert stateLock.isHeldByCurrentThread(); assert stateLock.isHeldByCurrentThread();
if (c instanceof PlainHttpConnection) { if (c instanceof PlainHttpConnection) {
removeFromPool(c, plainPool); return removeFromPool(c, plainPool);
} else { } else {
assert c.isSecure() : "connection " + c + " is not secure!"; assert c.isSecure() : "connection " + c + " is not secure!";
removeFromPool(c, sslPool); return removeFromPool(c, sslPool);
} }
} }
@ -529,13 +530,29 @@ final class ConnectionPool {
debug.log("%s : ConnectionPool.cleanup(%s)", debug.log("%s : ConnectionPool.cleanup(%s)",
String.valueOf(c.getConnectionFlow()), error); String.valueOf(c.getConnectionFlow()), error);
stateLock.lock(); stateLock.lock();
boolean removed;
try { try {
removeFromPool(c); removed = removeFromPool(c);
expiryList.remove(c); expiryList.remove(c);
} finally { } finally {
stateLock.unlock(); stateLock.unlock();
} }
c.close(); if (!removed) {
// this should not happen; the cleanup may have consumed
// some data that wasn't supposed to be consumed, so
// the only thing we can do is log it and close the
// connection.
if (Log.errors()) {
Log.logError("WARNING: CleanupTrigger triggered for" +
" a connection not found in the pool: closing {0}", c);
} else if (debug.on()) {
debug.log("WARNING: CleanupTrigger triggered for" +
" a connection not found in the pool: closing %s", c);
}
c.close(new IOException("Unexpected cleanup triggered for non pooled connection"));
} else {
c.close();
}
} }
/** /**
@ -549,6 +566,7 @@ final class ConnectionPool {
private final HttpConnection connection; private final HttpConnection connection;
private volatile boolean done; private volatile boolean done;
private volatile boolean dropped;
public CleanupTrigger(HttpConnection connection) { public CleanupTrigger(HttpConnection connection) {
this.connection = connection; this.connection = connection;
@ -566,6 +584,7 @@ final class ConnectionPool {
@Override @Override
public void onSubscribe(Flow.Subscription subscription) { public void onSubscribe(Flow.Subscription subscription) {
if (dropped || done) return;
subscription.request(1); subscription.request(1);
} }
@Override @Override
@ -586,5 +605,10 @@ final class ConnectionPool {
public String toString() { public String toString() {
return "CleanupTrigger(" + connection.getConnectionFlow() + ")"; return "CleanupTrigger(" + connection.getConnectionFlow() + ")";
} }
@Override
public void dropSubscription() {
dropped = true;
}
} }
} }

View File

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2017, 2023, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2017, 2024, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* This code is free software; you can redistribute it and/or modify it * This code is free software; you can redistribute it and/or modify it
@ -573,6 +573,8 @@ final class SocketTube implements FlowTube {
debug.log("read publisher: dropping pending subscriber: " debug.log("read publisher: dropping pending subscriber: "
+ previous.subscriber); + previous.subscriber);
previous.errorRef.compareAndSet(null, errorRef.get()); previous.errorRef.compareAndSet(null, errorRef.get());
// make sure no data will be routed to the old subscriber.
previous.stopReading();
previous.signalOnSubscribe(); previous.signalOnSubscribe();
if (subscriptionImpl.completed) { if (subscriptionImpl.completed) {
previous.signalCompletion(); previous.signalCompletion();
@ -606,6 +608,7 @@ final class SocketTube implements FlowTube {
volatile boolean subscribed; volatile boolean subscribed;
volatile boolean cancelled; volatile boolean cancelled;
volatile boolean completed; volatile boolean completed;
volatile boolean stopped;
public ReadSubscription(InternalReadSubscription impl, public ReadSubscription(InternalReadSubscription impl,
TubeSubscriber subscriber) { TubeSubscriber subscriber) {
@ -623,11 +626,11 @@ final class SocketTube implements FlowTube {
@Override @Override
public void request(long n) { public void request(long n) {
if (!cancelled) { if (!cancelled && !stopped) {
impl.request(n); impl.request(n);
} else { } else {
if (debug.on()) if (debug.on())
debug.log("subscription cancelled, ignoring request %d", n); debug.log("subscription stopped or cancelled, ignoring request %d", n);
} }
} }
@ -661,6 +664,20 @@ final class SocketTube implements FlowTube {
signalCompletion(); signalCompletion();
} }
} }
/**
* Called when switching subscriber on the {@link InternalReadSubscription}.
* This subscriber is the old subscriber. Demand on the internal
* subscription will be reset and reading will be paused until the
* new subscriber is subscribed.
* This should ensure that no data is routed to this subscriber
* until the new subscriber is subscribed.
*/
void stopReading() {
stopped = true;
impl.demand.reset();
impl.pauseReadEvent();
}
} }
final class InternalReadSubscription implements Flow.Subscription { final class InternalReadSubscription implements Flow.Subscription {

View File

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2018, 2023, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2018, 2024, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* This code is free software; you can redistribute it and/or modify it * This code is free software; you can redistribute it and/or modify it
@ -64,7 +64,7 @@ import static java.lang.String.format;
* @test * @test
* @summary this test verifies that a client may provides authorization * @summary this test verifies that a client may provides authorization
* headers directly when connecting with a server. * headers directly when connecting with a server.
* @bug 8087112 * @bug 8087112 8336655
* @library /test/lib /test/jdk/java/net/httpclient/lib * @library /test/lib /test/jdk/java/net/httpclient/lib
* @build jdk.httpclient.test.lib.common.HttpServerAdapters jdk.test.lib.net.SimpleSSLContext * @build jdk.httpclient.test.lib.common.HttpServerAdapters jdk.test.lib.net.SimpleSSLContext
* DigestEchoServer ReferenceTracker DigestEchoClient * DigestEchoServer ReferenceTracker DigestEchoClient