-
Notifications
You must be signed in to change notification settings - Fork 6.2k
8371475: HttpClient: Implement CUBIC congestion controller #28195
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Conversation
|
👋 Welcome back djelinski! A progress list of the required criteria for merging this PR into |
|
❗ This change is not yet ready to be integrated. |
|
@djelinski The following label will be automatically applied to this pull request:
When this pull request is ready to be reviewed, an "RFR" email will be sent to the corresponding mailing list. If you would like to change these labels, use the /label pull request command. |
|
If jdk.httpclient.quic.congestionController is exposed then we'll need to track this with a CSR. |
vy
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
|
|
||
| private final QuicPacer pacer; | ||
|
|
||
| class QuicRenoCongestionController extends QuicBaseCongestionController { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I guess thins can be final and consequently protected modifier can be removed from the implemented methods.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Changed the class to final. The protected modifier is still needed on overridden methods.
| boolean isAppLimited; | ||
| isAppLimited = congestionWindow > maxBytesInFlight + 2L * maxDatagramSize; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| boolean isAppLimited; | |
| isAppLimited = congestionWindow > maxBytesInFlight + 2L * maxDatagramSize; | |
| 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?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Suggestion applied.
MaxBytesInFlight is limited by the amount of available memory, and soft-limited to 16M (see MAX_BYTES_IN_FLIGHT in base congestion controller). While it can cross the limit briefly, I don't expect it to get anywhere near the arithmetic limits.
src/java.net.http/share/classes/jdk/internal/net/http/quic/QuicCubicCongestionController.java
Outdated
Show resolved
Hide resolved
src/java.net.http/share/classes/jdk/internal/net/http/quic/QuicCubicCongestionController.java
Show resolved
Hide resolved
src/java.net.http/share/classes/jdk/internal/net/http/quic/QuicCubicCongestionController.java
Outdated
Show resolved
Hide resolved
src/java.net.http/share/classes/jdk/internal/net/http/quic/QuicCubicCongestionController.java
Outdated
Show resolved
Hide resolved
src/java.net.http/share/classes/jdk/internal/net/http/quic/QuicConnectionImpl.java
Outdated
Show resolved
Hide resolved
|
The parent pull request that this pull request depends on has now been integrated and the target branch of this pull request has been updated. This means that changes from the dependent pull request can start to show up as belonging to this pull request, which may be confusing for reviewers. To remedy this situation, simply merge the latest changes from the new target branch into this pull request by running commands similar to these in the local repository for your personal fork: git checkout quic-cubic
git fetch https://git.openjdk.org/jdk.git master
git merge FETCH_HEAD
# if there are conflicts, follow the instructions given by git merge
git commit -m "Merge master"
git push |
|
@djelinski this pull request can not be integrated into git checkout quic-cubic
git fetch https://git.openjdk.org/jdk.git master
git merge FETCH_HEAD
# resolve conflicts and follow the instructions given by git merge
git commit -m "Merge master"
git push |
djelinski
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks @AlanBateman @vy for the reviews.
The property was not meant to be exposed; IMO CUBIC is superior to Reno in all aspects that matter. I renamed it to include internal.
We will probably need to revisit the configuration if and when we implement BBR, but we aren't quite there yet.
src/java.net.http/share/classes/jdk/internal/net/http/quic/QuicConnectionImpl.java
Outdated
Show resolved
Hide resolved
src/java.net.http/share/classes/jdk/internal/net/http/quic/QuicCubicCongestionController.java
Outdated
Show resolved
Hide resolved
src/java.net.http/share/classes/jdk/internal/net/http/quic/QuicCubicCongestionController.java
Outdated
Show resolved
Hide resolved
src/java.net.http/share/classes/jdk/internal/net/http/quic/QuicCubicCongestionController.java
Outdated
Show resolved
Hide resolved
|
|
||
| private final QuicPacer pacer; | ||
|
|
||
| class QuicRenoCongestionController extends QuicBaseCongestionController { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Changed the class to final. The protected modifier is still needed on overridden methods.
| boolean isAppLimited; | ||
| isAppLimited = congestionWindow > maxBytesInFlight + 2L * maxDatagramSize; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Suggestion applied.
MaxBytesInFlight is limited by the amount of available memory, and soft-limited to 16M (see MAX_BYTES_IN_FLIGHT in base congestion controller). While it can cross the limit briefly, I don't expect it to get anywhere near the arithmetic limits.
src/java.net.http/share/classes/jdk/internal/net/http/quic/QuicCubicCongestionController.java
Show resolved
Hide resolved
src/java.net.http/share/classes/jdk/internal/net/http/quic/QuicBaseCongestionController.java
Show resolved
Hide resolved
| protected long congestionWindow = INITIAL_WINDOW; | ||
| protected int maxDatagramSize = QuicConnectionImpl.DEFAULT_DATAGRAM_SIZE; | ||
| protected int minimumWindow = 2 * maxDatagramSize; | ||
| protected long bytesInFlight; | ||
| // maximum bytes in flight seen since the last congestion event | ||
| protected long maxBytesInFlight; | ||
| protected Deadline congestionRecoveryStartTime; | ||
| protected long ssThresh = Long.MAX_VALUE; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we remove the protected keyword on the mutable fields? All the subclasses are in the same package.
| protected QuicBaseCongestionController(String dbgTag, QuicRttEstimator rttEstimator) { | ||
| this.dbgTag = dbgTag; | ||
| this.timeSource = TimeSource.source(); | ||
| this.pacer = new QuicPacer(rttEstimator, this); | ||
| } | ||
|
|
||
| // for testing | ||
| protected QuicBaseCongestionController(TimeLine source, QuicRttEstimator rttEstimator) { | ||
| this.dbgTag = "TEST"; | ||
| this.timeSource = source; | ||
| this.pacer = new QuicPacer(rttEstimator, this); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| protected QuicBaseCongestionController(String dbgTag, QuicRttEstimator rttEstimator) { | |
| this.dbgTag = dbgTag; | |
| this.timeSource = TimeSource.source(); | |
| this.pacer = new QuicPacer(rttEstimator, this); | |
| } | |
| // for testing | |
| protected QuicBaseCongestionController(TimeLine source, QuicRttEstimator rttEstimator) { | |
| this.dbgTag = "TEST"; | |
| this.timeSource = source; | |
| this.pacer = new QuicPacer(rttEstimator, this); | |
| } | |
| protected QuicBaseCongestionController(String dbgTag, QuicRttEstimator rttEstimator) { | |
| this(dbgTag, TimeSource.source(), rttEstimator); | |
| } | |
| // Allows to pass a custom TimeLine when testing | |
| QuicBaseCongestionController(String dbgTag, TimeLine source, QuicRttEstimator rttEstimator) { | |
| this.dbgTag = dbgTag; | |
| this.timeSource = source; | |
| this.pacer = new QuicPacer(rttEstimator, this); | |
| } |
|
|
||
| // for testing | ||
| public QuicCubicCongestionController(TimeLine source, QuicRttEstimator rttEstimator) { | ||
| super(source, rttEstimator); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| super(source, rttEstimator); | |
| super("TEST", source, rttEstimator); |
| boolean isAppLimited = sentTime.isAfter(lastFullWindow); | ||
| if (!isAppLimited) { | ||
| if (wEstBytes < cwndPriorBytes) { | ||
| wEstBytes += Math.max((long) (ALPHA * maxDatagramSize * packetBytes / congestionWindow), 1); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
should we assert that congestionWindow is > 2 ?
| if (dblTargetBytes > 1.5 * congestionWindow) { | ||
| targetBytes = (long) (1.5 * congestionWindow); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| if (dblTargetBytes > 1.5 * congestionWindow) { | |
| targetBytes = (long) (1.5 * congestionWindow); | |
| // targetLimit is 1.5 * congestionWindow | |
| long targetLimit = congestionWindow + (congestionWindow >> 1) | |
| if (dblTargetBytes > targetLimit) { | |
| targetBytes = targetLimit; |
| targetBytes = (long)dblTargetBytes; | ||
| } | ||
| if (targetBytes > congestionWindow) { | ||
| congestionWindow += Math.max((targetBytes - congestionWindow) * packetBytes / congestionWindow, 1L); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can (targetBytes - congestionWindow) * packetBytes / congestionWindow overflow?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we assert that congestionWindow is >= minimumWindow after this operation?
| // set lastFullWindow to prevent rapid timeNanos growth | ||
| lastFullWindow = congestionRecoveryStartTime; | ||
| // ((wmax_segments - cwnd_segments) / C) ^ (1/3) seconds | ||
| kNanos = (long)(Math.cbrt((wMaxBytes - congestionWindow) / C / maxDatagramSize) * 1_000_000_000); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
should we assert that wMaxBytes >= congestionWindow ? It is not immediately obvious to me that this is guaranteed.
In particular:
if (congestionWindow < wMaxBytes) {
// fast convergence
wMaxBytes = (long) ((1 + BETA) * congestionWindow / 2);
seems to imply that wMaxBytes will be less than congestion window since 1.7 < 2
So we would end up with a negative value for kNanos, which is defined as:
The time period in seconds it takes to increase the congestion window size at the beginning of the current congestion avoidance stage to Wmax.
so presumably negative values for kNanos are not expected?
|
And another question: after this change - do we still have tests for the Reno congestion controller? Or is everthing using Cubic? |
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.congestionControllertoreno.A new test was added to exercise the new congestion controller. Existing tests continue to pass.
Progress
Issue
Reviewing
Using
gitCheckout this PR locally:
$ git fetch https://git.openjdk.org/jdk.git pull/28195/head:pull/28195$ git checkout pull/28195Update a local copy of the PR:
$ git checkout pull/28195$ git pull https://git.openjdk.org/jdk.git pull/28195/headUsing Skara CLI tools
Checkout this PR locally:
$ git pr checkout 28195View PR using the GUI difftool:
$ git pr show -t 28195Using diff file
Download this PR as a diff file:
https://git.openjdk.org/jdk/pull/28195.diff
Using Webrev
Link to Webrev Comment