From 386ad61458a3901622b92ca56982d728c11b846a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Jeli=C5=84ski?= Date: Wed, 17 Dec 2025 07:49:58 +0000 Subject: [PATCH] 8373409: java/net/httpclient/http3/H3ErrorHandlingTest.java failed due to deadlock Reviewed-by: dfuchs --- .../internal/net/http/quic/QuicEndpoint.java | 66 +++++++++---------- .../httpclient/http3/H3ErrorHandlingTest.java | 1 + 2 files changed, 31 insertions(+), 36 deletions(-) diff --git a/src/java.net.http/share/classes/jdk/internal/net/http/quic/QuicEndpoint.java b/src/java.net.http/share/classes/jdk/internal/net/http/quic/QuicEndpoint.java index b1de5ef4bfd..8bdba21594c 100644 --- a/src/java.net.http/share/classes/jdk/internal/net/http/quic/QuicEndpoint.java +++ b/src/java.net.http/share/classes/jdk/internal/net/http/quic/QuicEndpoint.java @@ -1581,34 +1581,30 @@ public abstract sealed class QuicEndpoint implements AutoCloseable peerIssuedResetTokens.replaceAll((tok, c) -> c == from ? to : c); } - public void draining(final QuicPacketReceiver connection) { + public void draining(final QuicConnectionImpl connection) { // remap the connection to a DrainingConnection if (closed) return; + + final long idleTimeout = connection.peerPtoMs() * 3; // 3 PTO + connection.localConnectionIdManager().close(); + DrainingConnection draining = new DrainingConnection(connection.connectionIds(), idleTimeout); + // we can ignore stateless reset in the draining state. + remapPeerIssuedResetToken(connection, draining); + connection.connectionIds().forEach((id) -> - connections.compute(id, this::remapDraining)); + connections.compute(id, (i, r) -> remapDraining(i, r, draining))); + draining.startTimer(); assert !connections.containsValue(connection) : connection; } - private DrainingConnection remapDraining(QuicConnectionId id, QuicPacketReceiver conn) { + private DrainingConnection remapDraining(QuicConnectionId id, QuicPacketReceiver conn, DrainingConnection draining) { if (closed) return null; var debugOn = debug.on() && !Thread.currentThread().isVirtual(); - if (conn instanceof ClosingConnection closing) { + if (conn instanceof QuicConnectionImpl || conn instanceof ClosingConnection) { if (debugOn) debug.log("remapping %s to DrainingConnection", id); - final var draining = closing.toDraining(); - remapPeerIssuedResetToken(closing, draining); - draining.startTimer(); - return draining; - } else if (conn instanceof DrainingConnection draining) { - return draining; - } else if (conn instanceof QuicConnectionImpl impl) { - final long idleTimeout = impl.peerPtoMs() * 3; // 3 PTO - impl.localConnectionIdManager().close(); - if (debugOn) debug.log("remapping %s to DrainingConnection", id); - var draining = new DrainingConnection(conn.connectionIds(), idleTimeout); - // we can ignore stateless reset in the draining state. - remapPeerIssuedResetToken(impl, draining); - draining.startTimer(); return draining; + } else if (conn instanceof DrainingConnection d) { + return d; } else if (conn == null) { // connection absent (was probably removed), don't remap to draining if (debugOn) { @@ -1623,30 +1619,32 @@ public abstract sealed class QuicEndpoint implements AutoCloseable protected void closing(QuicConnectionImpl connection, ByteBuffer datagram) { if (closed) return; - ByteBuffer closing = ByteBuffer.allocate(datagram.limit()); - closing.put(datagram.slice()); - closing.flip(); + ByteBuffer closingDatagram = ByteBuffer.allocate(datagram.limit()); + closingDatagram.put(datagram.slice()); + closingDatagram.flip(); + + final long idleTimeout = connection.peerPtoMs() * 3; // 3 PTO + connection.localConnectionIdManager().close(); + var closingConnection = new ClosingConnection(connection.connectionIds(), idleTimeout, datagram); + remapPeerIssuedResetToken(connection, closingConnection); + connection.connectionIds().forEach((id) -> - connections.compute(id, (i, r) -> remapClosing(i, r, closing))); + connections.compute(id, (i, r) -> remapClosing(i, r, closingConnection))); + closingConnection.startTimer(); assert !connections.containsValue(connection) : connection; } - private ClosedConnection remapClosing(QuicConnectionId id, QuicPacketReceiver conn, ByteBuffer datagram) { + private ClosedConnection remapClosing(QuicConnectionId id, QuicPacketReceiver conn, ClosingConnection closingConnection) { if (closed) return null; var debugOn = debug.on() && !Thread.currentThread().isVirtual(); - if (conn instanceof ClosingConnection closing) { + if (conn instanceof QuicConnectionImpl) { + if (debugOn) debug.log("remapping %s to ClosingConnection", id); + return closingConnection; + } else if (conn instanceof ClosingConnection closing) { // we already have a closing datagram, drop the new one return closing; } else if (conn instanceof DrainingConnection draining) { return draining; - } else if (conn instanceof QuicConnectionImpl impl) { - final long idleTimeout = impl.peerPtoMs() * 3; // 3 PTO - impl.localConnectionIdManager().close(); - if (debugOn) debug.log("remapping %s to ClosingConnection", id); - var closing = new ClosingConnection(conn.connectionIds(), idleTimeout, datagram); - remapPeerIssuedResetToken(impl, closing); - closing.startTimer(); - return closing; } else if (conn == null) { // connection absent (was probably removed), don't remap to closing if (debugOn) { @@ -1896,10 +1894,6 @@ public abstract sealed class QuicEndpoint implements AutoCloseable debug.log("ClosingConnection(%s): dropping %s packet", localConnectionIds, headersType); } } - - private DrainingConnection toDraining() { - return new DrainingConnection(localConnectionIds, maxIdleTimeMs); - } } /** diff --git a/test/jdk/java/net/httpclient/http3/H3ErrorHandlingTest.java b/test/jdk/java/net/httpclient/http3/H3ErrorHandlingTest.java index 4315fd36de5..bb5aeacbb0c 100644 --- a/test/jdk/java/net/httpclient/http3/H3ErrorHandlingTest.java +++ b/test/jdk/java/net/httpclient/http3/H3ErrorHandlingTest.java @@ -71,6 +71,7 @@ import static org.testng.Assert.*; /* * @test + * @bug 8373409 * @key intermittent * @comment testResetControlStream may fail if the client doesn't read the stream type * before the stream is reset,