implement a token-bucket pacing algorithm #2615
Merged
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Fixes #2606. cc @Stebalien
The bucket accumulates "tokens" (bytes that can be sent out immediately). Every time we send a packet, we check if there are enough tokens in the bucket to send out a (full-size) packet.
Once the tokens in the bucket are depleted, we need to wait for enough new tokens to accumulate, before we can send out another packet.
TimeUntilSend
can be used to query when this will be the case. To avoid setting too short timers (this would both be computationally inefficient, and timers have a limited resolution anyway), this function will never return a duration smaller thanMinPacingDelay
(1ms).When sending out packets, we arm a pacing timer if the bucket runs out of tokens and we're not congestion limited. If we're congestion limited, but still have enough tokens in the bucket, there's no need to arm a timer, as the only way we'll be allowed to send more packets is by receiving an ACK from the peer that frees up the congestion window.
To avoid sending large bursts into the network, the bucket size is limited to maximum of a constant
maxBurstSize
(10 full-size packets) and the number of packets that we're supposed to send out duringMinPacingDelay + TimerGranularity
(both 1ms).As recommended in the recovery draft, the pacer fills the bucket with a slightly higher rate than the actual bandwidth (
N = 1.25
). This allows us to completely fill the congestion window even when the RTT varies. More importantly, it also means that in the case of a continuous transfer, we become congestion limited shortly before depleting the bucket. As described above, in this case we don't need to arm a pacing timer, as packets are effectively paced by receiving acknowledgements. This greatly reduces the number of times we need to arm the session timer.In my tests, this PR reduces the number of times we arm a pacing timer by 2/3 on the sending side, and eliminates the setting of pacing timers completely on the receiving side (as it should, there's nothing to pace when you're just acknowledging incoming packets).