Skip to content

Commit 7a7e7c9

Browse files
committed
8373877: QUIC connections are removed too early
Reviewed-by: dfuchs
1 parent b848ddf commit 7a7e7c9

File tree

4 files changed

+22
-8
lines changed

4 files changed

+22
-8
lines changed

src/java.net.http/share/classes/jdk/internal/net/http/quic/ConnectionTerminatorImpl.java

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -344,10 +344,6 @@ private void drain(final ConnectionCloseFrame incomingFrame) {
344344
}
345345
}
346346
failHandshakeCFs();
347-
// remap the connection to a draining connection
348-
final QuicEndpoint endpoint = this.connection.endpoint();
349-
assert endpoint != null : "QUIC endpoint is null";
350-
endpoint.draining(connection);
351347
discardConnectionState();
352348
connection.streams.terminate(terminationCause);
353349
if (Log.quic()) {
@@ -439,7 +435,7 @@ private void pushConnectionCloseFrame(final KeySpace keySpace,
439435
final ProtectionRecord protectionRecord = ProtectionRecord.single(packet,
440436
connection::allocateDatagramForEncryption);
441437
// while sending the packet containing the CONNECTION_CLOSE frame, the pushDatagram will
442-
// remap (or remove) the QuicConnectionImpl in QuicEndpoint.
438+
// remap the QuicConnectionImpl in QuicEndpoint.
443439
connection.pushDatagram(protectionRecord);
444440
}
445441

src/java.net.http/share/classes/jdk/internal/net/http/quic/QuicConnectionImpl.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2811,7 +2811,7 @@ private void pushEncryptedDatagram(final ProtectionRecord protectionRecord) {
28112811
// a CONNECTION_CLOSE frame is being sent to the peer when the local
28122812
// connection state is in DRAINING. This implies that the local endpoint
28132813
// is responding to an incoming CONNECTION_CLOSE frame from the peer.
2814-
// we remove the connection from the endpoint for such cases.
2814+
// we switch this connection to one that does not respond to incoming packets.
28152815
endpoint.pushClosedDatagram(this, peerAddress(), datagram);
28162816
} else if (stateHandle.isMarked(QuicConnectionState.CLOSING)) {
28172817
// a CONNECTION_CLOSE frame is being sent to the peer when the local

src/java.net.http/share/classes/jdk/internal/net/http/quic/QuicEndpoint.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1509,7 +1509,8 @@ public void pushClosingDatagram(QuicConnectionImpl connection, InetSocketAddress
15091509
/**
15101510
* Called to schedule sending of a datagram that contains a single {@code ConnectionCloseFrame}
15111511
* sent in response to a {@code ConnectionClose} frame.
1512-
* This will completely remove the connection from the connection map.
1512+
* This will replace the {@link QuicConnectionImpl} with a {@link DrainingConnection} that
1513+
* will discard all incoming packets.
15131514
* @param connection the connection being closed
15141515
* @param destination the peer address
15151516
* @param datagram the datagram
@@ -1518,7 +1519,7 @@ public void pushClosedDatagram(QuicConnectionImpl connection,
15181519
InetSocketAddress destination,
15191520
ByteBuffer datagram) {
15201521
if (debug.on()) debug.log("Pushing closed datagram for " + connection.logTag());
1521-
removeConnection(connection);
1522+
draining(connection);
15221523
pushDatagram(connection, destination, datagram);
15231524
}
15241525

test/jdk/java/net/httpclient/quic/StatelessResetReceiptTest.java

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@
4747
import jdk.internal.net.http.quic.QuicClient;
4848
import jdk.internal.net.http.quic.QuicConnectionId;
4949
import jdk.internal.net.http.quic.QuicConnectionImpl;
50+
import jdk.internal.net.http.quic.QuicTransportParameters;
5051
import jdk.internal.net.http.quic.TerminationCause;
5152
import jdk.internal.net.quic.QuicTLSContext;
5253
import jdk.internal.net.quic.QuicVersion;
@@ -58,11 +59,13 @@
5859
import static jdk.internal.net.quic.QuicTransportErrors.NO_VIABLE_PATH;
5960
import static org.junit.jupiter.api.Assertions.assertEquals;
6061
import static org.junit.jupiter.api.Assertions.assertFalse;
62+
import static org.junit.jupiter.api.Assertions.assertNotEquals;
6163
import static org.junit.jupiter.api.Assertions.assertNotNull;
6264
import static org.junit.jupiter.api.Assertions.assertTrue;
6365

6466
/*
6567
* @test
68+
* @bug 8373877
6669
* @summary verify that when a QUIC (client) connection receives a stateless reset
6770
* from the peer, then the connection is properly terminated
6871
* @library /test/lib /test/jdk/java/net/httpclient/lib
@@ -87,6 +90,10 @@ static void beforeAll() throws Exception {
8790
.availableVersions(new QuicVersion[]{QuicVersion.QUIC_V1})
8891
.sslContext(sslContext)
8992
.build();
93+
// Use a longer max ack delay to inflate the draining time (3xPTO)
94+
final QuicTransportParameters transportParameters = new QuicTransportParameters();
95+
transportParameters.setIntParameter(QuicTransportParameters.ParameterId.max_ack_delay,
96+
(1 << 14) - 1); // 16 seconds, maximum allowed
9097
server.start();
9198
System.out.println("Server started at " + server.getAddress());
9299
}
@@ -179,6 +186,9 @@ public void testClosingConnection() throws Exception {
179186
conn.close();
180187
// verify connection is no longer open
181188
assertFalse(conn.underlyingQuicConnection().isOpen(), "QUIC connection is still open");
189+
// Check that the (closing) connection is still registered with the endpoint
190+
assertNotEquals(0, conn.endpoint().connectionCount(),
191+
"Expected the QUIC connection to be registered");
182192
// now send a stateless reset from the server connection
183193
sendStatelessResetFrom(serverConn);
184194
// wait for the stateless reset to be processed
@@ -231,12 +241,19 @@ public void testDrainingConnection() throws Exception {
231241
.loggedAs("intentionally closed by server to initiate draining state" +
232242
" on client connection");
233243
serverConn.connectionTerminator().terminate(tc);
244+
// send ping in case the connection_close message gets lost
245+
conn.underlyingQuicConnection().requestSendPing();
234246
// wait for client conn to terminate
235247
final TerminationCause clientTC = ((QuicConnectionImpl) conn.underlyingQuicConnection())
236248
.futureTerminationCause().get();
237249
// verify connection closed for the right reason
238250
assertEquals(NO_VIABLE_PATH.code(), clientTC.getCloseCode(),
239251
"unexpected termination cause");
252+
// wait for a while to let the connection close completely
253+
Thread.sleep(10);
254+
// Check that the (draining) connection is still registered with the endpoint
255+
assertNotEquals(0, conn.endpoint().connectionCount(),
256+
"Expected the QUIC connection to be registered");
240257
// now send a stateless reset from the server connection
241258
sendStatelessResetFrom(serverConn);
242259
// wait for the stateless reset to be processed

0 commit comments

Comments
 (0)