RFR: 8371475: HttpClient: Implement CUBIC congestion controller

Volkan Yazici vyazici at openjdk.org
Fri Nov 7 17:30:12 UTC 2025


On Fri, 7 Nov 2025 13:38:23 GMT, Daniel Jeliński <djelinski at openjdk.org> wrote:

>> CUBIC is a standard TCP congestion control algorithm that uses a cubic function instead of a linear congestion window increase function to improve scalability and stability over fast and long-distance networks. CUBIC has been adopted as the default TCP congestion control algorithm by the Linux, Windows, and Apple stacks.
> 
> This PR adds a new congestion controller algorithm. It reuses a large part of the QuicRenoCongestionController, which was refactored to two classes - QuicBaseCongestionController, containing the shared code, and QuicRenoCongestionController, containing only the code that is unique to Reno.
> 
> CUBIC is now the default congestion controller. Reno can still be selected by setting the system property `jdk.httpclient.quic.congestionController` to `reno`.
> 
> A new test was added to exercise the new congestion controller. Existing tests continue to pass.

Really neat work @djelinski! I liked the surgery you carried out in `QuicBaseCC`. 💯

I've dropped some minor remarks. I guess we will need some time to wrap our mind around the math involved in the PR.

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

> 369:     private static QuicCongestionController createCongestionController
> 370:             (String dbgTag, QuicRttEstimator rttEstimator) {
> 371:         String algo = System.getProperty("jdk.httpclient.quic.congestionController", "cubic");

Can we keep this internal? That is, can we use `jdk.internal.httpclient.quic.congestionController` instead?

src/java.net.http/share/classes/jdk/internal/net/http/quic/QuicCubicCongestionController.java line 41:

> 39:  *       RFC 9438: CUBIC for Fast and Long-Distance Networks
> 40:  */
> 41: public class QuicCubicCongestionController extends QuicBaseCongestionController {

I guess this can be `final`.

src/java.net.http/share/classes/jdk/internal/net/http/quic/QuicCubicCongestionController.java line 62:

> 60:     public QuicCubicCongestionController(String dbgTag, QuicRttEstimator rttEstimator) {
> 61:         super(dbgTag, rttEstimator);
> 62:         this.rttEstimator = rttEstimator;

Can we make `rttEstimator` a `protected` field of `QuicBaseCongestionController`?

src/java.net.http/share/classes/jdk/internal/net/http/quic/QuicCubicCongestionController.java line 112:

> 110:     protected boolean congestionAvoidanceAcked(int packetBytes, Deadline sentTime) {
> 111:         boolean isAppLimited;
> 112:         isAppLimited = sentTime.isAfter(lastFullWindow);

Suggestion:

        boolean isAppLimited = sentTime.isAfter(lastFullWindow);

src/java.net.http/share/classes/jdk/internal/net/http/quic/QuicCubicCongestionController.java line 122:

> 120:             // target = Wcubic(t)
> 121:             // this is less aggressive than RFC 9438, which uses target=Wcubic(t+RTT),
> 122:             // but seems to work well enough

Shall we also document why we deviate from the RFC?

src/java.net.http/share/classes/jdk/internal/net/http/quic/QuicRenoCongestionController.java line 40:

> 38:  *      RFC 9002: QUIC Loss Detection and Congestion Control
> 39:  */
> 40: class QuicRenoCongestionController extends QuicBaseCongestionController {

I guess thins can be `final` and consequently `protected` modifier can be removed from the implemented methods.

src/java.net.http/share/classes/jdk/internal/net/http/quic/QuicRenoCongestionController.java line 47:

> 45:     protected boolean congestionAvoidanceAcked(int packetBytes, Deadline sentTime) {
> 46:         boolean isAppLimited;
> 47:         isAppLimited = congestionWindow > maxBytesInFlight + 2L * maxDatagramSize;

Suggestion:

        boolean isAppLimited = congestionWindow > maxBytesInFlight + 2L * maxDatagramSize;


@djelinski, I see you verbatim copied these lines – which is fine, and makes things easier to review. Nevertheless, I want to double-check: do we need to guard against any arithmetic overflows here?

test/jdk/java/net/httpclient/quic/CubicTest.java line 42:

> 40: /*
> 41:  * @test
> 42:  * @run testng/othervm -Djdk.httpclient.HttpClient.log=trace,quic:cc CubicTest

Shall we use JUnit instead?

-------------

PR Review: https://git.openjdk.org/jdk/pull/28195#pullrequestreview-3435245896
PR Review Comment: https://git.openjdk.org/jdk/pull/28195#discussion_r2504693701
PR Review Comment: https://git.openjdk.org/jdk/pull/28195#discussion_r2504645030
PR Review Comment: https://git.openjdk.org/jdk/pull/28195#discussion_r2504653829
PR Review Comment: https://git.openjdk.org/jdk/pull/28195#discussion_r2504673398
PR Review Comment: https://git.openjdk.org/jdk/pull/28195#discussion_r2504672752
PR Review Comment: https://git.openjdk.org/jdk/pull/28195#discussion_r2504622773
PR Review Comment: https://git.openjdk.org/jdk/pull/28195#discussion_r2504632463
PR Review Comment: https://git.openjdk.org/jdk/pull/28195#discussion_r2504680316


More information about the net-dev mailing list