Skip to content

Commit f75774f

Browse files
committed
8386985: PacketSpaceManagerTest failed with AssertionError; A race condition may cause packetSent to mistakenly skip rescheduling of the transmitter task
1 parent 0c709fd commit f75774f

2 files changed

Lines changed: 77 additions & 29 deletions

File tree

‎src/java.net.http/share/classes/jdk/internal/net/http/quic/PacketSpaceManager.java‎

Lines changed: 7 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2021, 2025, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2021, 2026, Oracle and/or its affiliates. All rights reserved.
33
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
44
*
55
* This code is free software; you can redistribute it and/or modify it
@@ -645,22 +645,15 @@ private synchronized boolean shouldLogWhenNewDeadline() {
645645
return false;
646646
}
647647

648-
boolean hasNoDeadline() {
649-
return Deadline.MAX.equals(nextDeadline);
650-
}
651-
652648
// reschedule this task
653649
void reschedule() {
654650
Deadline deadline = computeNextDeadline();
655-
Deadline nextDeadline = this.nextDeadline;
656651
if (Deadline.MAX.equals(deadline)) {
657-
debug.log("no deadline, don't reschedule");
658-
} else if (deadline.equals(nextDeadline)) {
659-
debug.log("deadline unchanged, don't reschedule");
660-
} else {
661-
packetEmitter.reschedule(this, deadline);
662-
debug.log("retransmission task: rescheduled");
652+
if (debug.on()) debug.log("no deadline, don't reschedule");
653+
return;
663654
}
655+
if (debug.on()) debug.log("retransmission task: rescheduled");
656+
packetEmitter.reschedule(this, deadline);
664657
}
665658

666659
@Override
@@ -1304,7 +1297,7 @@ public void packetSent(QuicPacket packet, long previousPacketNumber, long packet
13041297
} finally {
13051298
transferLock.unlock();
13061299
}
1307-
if (found && packetTransmissionTask.hasNoDeadline()) {
1300+
if (found) {
13081301
packetTransmissionTask.reschedule();
13091302
}
13101303
if (!found) {
@@ -1340,9 +1333,7 @@ public void packetSent(QuicPacket packet, long previousPacketNumber, long packet
13401333
return;
13411334
}
13421335
addAcknowledgement(pending);
1343-
if (packetTransmissionTask.hasNoDeadline()) {
1344-
packetTransmissionTask.reschedule();
1345-
}
1336+
packetTransmissionTask.reschedule();
13461337
} finally {
13471338
transferLock.unlock();
13481339
}

‎test/jdk/java/net/httpclient/quic/PacketSpaceManagerTest.java‎

Lines changed: 70 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -80,16 +80,19 @@
8080

8181
import static org.junit.jupiter.api.Assertions.assertEquals;
8282
import static org.junit.jupiter.api.Assertions.assertFalse;
83+
import static org.junit.jupiter.api.Assertions.assertNotEquals;
8384
import static org.junit.jupiter.api.Assertions.assertNotNull;
8485
import static org.junit.jupiter.api.Assertions.assertNull;
8586
import static org.junit.jupiter.api.Assertions.assertTrue;
8687

8788
import org.junit.jupiter.api.Assertions;
89+
import org.junit.jupiter.api.Test;
8890
import org.junit.jupiter.params.ParameterizedTest;
8991
import org.junit.jupiter.params.provider.MethodSource;
9092

9193
/*
9294
* @test
95+
* @bug 8349910 8386985
9396
* @summary tests the logic to build an AckFrame
9497
* @library /test/lib
9598
* @library ../debug
@@ -102,6 +105,7 @@
102105
* @run junit/othervm -Dseed=-4159871071396382784 ${test.main.class}
103106
* @run junit/othervm -Dseed=2252276218459363615 ${test.main.class}
104107
* @run junit/othervm -Dseed=-5130588140709404919 ${test.main.class}
108+
* @run junit/othervm -Dseed=4257295716830862528 ${test.main.class}
105109
*/
106110
// -Djdk.internal.httpclient.debug=true
107111
public class PacketSpaceManagerTest {
@@ -766,6 +770,22 @@ boolean isDue(Deadline now) {
766770
}
767771
}
768772

773+
public List<QuicFrame> sendPacket(long offset, AckFrame ackFrameToSend, Packet packet, long largestReceivedAckedPN) {
774+
// add a crypto frame and build the packet
775+
CryptoFrame crypto = new CryptoFrame(offset, 1,
776+
ByteBuffer.wrap(new byte[] {nextByte(offset)}));
777+
List<QuicFrame> frames = ackFrameToSend == null ?
778+
List.of(crypto) : List.of(crypto, ackFrameToSend);
779+
QuicPacket newPacket = codingContext.encoder
780+
.newInitialPacket(localId, peerId,
781+
null,
782+
packet.packetNumber,
783+
largestReceivedAckedPN,
784+
frames, codingContext);
785+
// pretend that we sent a packet
786+
manager.packetSent(newPacket, -1, packet.packetNumber);
787+
return frames;
788+
}
769789
/**
770790
* Drives the test by pretending to emit each packet in order,
771791
* then pretending to receive ack frames (as soon as possible
@@ -830,19 +850,8 @@ public void run() throws Exception {
830850
debug.log("largestAckSent is: " + largestAckAcked);
831851
}
832852

833-
// add a crypto frame and build the packet
834-
CryptoFrame crypto = new CryptoFrame(offset, 1,
835-
ByteBuffer.wrap(new byte[] {nextByte(offset)}));
836-
List<QuicFrame> frames = ackFrameToSend == null ?
837-
List.of(crypto) : List.of(crypto, ackFrameToSend);
838-
QuicPacket newPacket = codingContext.encoder
839-
.newInitialPacket(localId, peerId,
840-
null,
841-
packet.packetNumber,
842-
largestReceivedAckedPN,
843-
frames, codingContext);
844-
// pretend that we sent a packet
845-
manager.packetSent(newPacket, -1, packet.packetNumber);
853+
// send a packet
854+
List<QuicFrame> frames = sendPacket(offset, ackFrameToSend, packet, largestReceivedAckedPN);
846855

847856
// compute next deadline
848857
var nextDeadline = timerQueue.nextDeadline();
@@ -1125,4 +1134,52 @@ public void testPacketSpaceManager(TestCase testCase) throws Exception {
11251134
driver.check();
11261135
}
11271136

1137+
@Test
1138+
public void testPacketSent() throws Exception {
1139+
// this test case is specifically for JDK-8386985
1140+
System.out.printf("%n ------- testPacketSent ------- %n");
1141+
1142+
// create a minimal SynchronousTestDriver
1143+
TestCase testCase = new TestCase(List.of(new Acknowledged(1, 3), new Acknowledged(4,4)),
1144+
List.of(new Packet(3, 0), new Packet(4, 0)));
1145+
SynchronousTestDriver driver = new SynchronousTestDriver(testCase);
1146+
1147+
// send a first ack-eliciting packet, and move the timeline past PTO
1148+
driver.sendPacket(0, null, new Packet(1, 0), -1);
1149+
Deadline pto = driver.manager.nextScheduledDeadline(); // should be PTO
1150+
driver.timeSource.advance(driver.timeSource.instant().until(pto, ChronoUnit.MILLIS) + 250, ChronoUnit.MILLIS);
1151+
1152+
// start processing events, but delay the task that will run the transmitter
1153+
ArrayList<Runnable> tasks = new ArrayList<>();
1154+
Executor executor = new Executor() {
1155+
@Override
1156+
public void execute(Runnable command) {
1157+
tasks.add(command);
1158+
}
1159+
};
1160+
driver.timerQueue.processEventsAndReturnNextDeadline(driver.timeSource.instant(), executor);
1161+
1162+
// acknowledge the first packet so that it's no longer pending retransmission
1163+
driver.manager.processAckFrame(new AckFrameBuilder().addAck(1).build());
1164+
1165+
// send a second packet, and examine the timerQueue next deadline
1166+
// if sending the second packet didn't cause the task to be rescheduled, we
1167+
// will observe Deadline.MAX, or a deadline before now: that's the bug.
1168+
driver.sendPacket(1, null, new Packet(2, 0), -1);
1169+
1170+
Deadline next = driver.timerQueue.nextDeadline();
1171+
assertNotEquals(next, Deadline.MAX);
1172+
assertTrue(next.isAfter(driver.timeSource.instant()));
1173+
1174+
// now finish running the task and ack the second packet,
1175+
// so that we leave the packet space manager in a clean state
1176+
// for running the driver with the next two packets.
1177+
for (Runnable task : tasks) {
1178+
task.run();
1179+
}
1180+
driver.manager.processAckFrame(new AckFrameBuilder().addAck(1).addAck(2).build());
1181+
driver.run();
1182+
driver.check();
1183+
}
1184+
11281185
}

0 commit comments

Comments
 (0)