@@ -18,18 +18,20 @@ pub(super) struct Pacer {
18
18
last_mtu : u16 ,
19
19
tokens : u64 ,
20
20
prev : Instant ,
21
+ burst_mode : bool ,
21
22
}
22
23
23
24
impl Pacer {
24
25
/// Obtains a new [`Pacer`].
25
26
pub ( super ) fn new ( smoothed_rtt : Duration , window : u64 , mtu : u16 , now : Instant ) -> Self {
26
- let capacity = optimal_capacity ( smoothed_rtt, window, mtu) ;
27
+ let capacity = optimal_capacity ( smoothed_rtt, window, mtu, true ) ;
27
28
Self {
28
29
capacity,
29
30
last_window : window,
30
31
last_mtu : mtu,
31
32
tokens : capacity,
32
33
prev : now,
34
+ burst_mode : true ,
33
35
}
34
36
}
35
37
@@ -43,6 +45,8 @@ impl Pacer {
43
45
/// If we can send a packet right away, this returns `None`. Otherwise, returns `Some(d)`,
44
46
/// where `d` is the time before this function should be called again.
45
47
///
48
+ /// In slow mode, it fills the bucket progressively by yielding at a maximum of 10 ms.
49
+ ///
46
50
/// The 5/4 ratio used here comes from the suggestion that N = 1.25 in the draft IETF RFC for
47
51
/// QUIC.
48
52
pub ( super ) fn delay (
@@ -59,10 +63,13 @@ impl Pacer {
59
63
) ;
60
64
61
65
if window != self . last_window || mtu != self . last_mtu {
62
- self . capacity = optimal_capacity ( smoothed_rtt, window, mtu) ;
66
+ self . burst_mode = is_burst_mode ( smoothed_rtt, window, mtu) ;
67
+ self . capacity = optimal_capacity ( smoothed_rtt, window, mtu, self . burst_mode ) ;
63
68
64
- // Clamp the tokens
65
- self . tokens = self . capacity . min ( self . tokens ) ;
69
+ // Clamp the tokens in burst mode
70
+ if self . burst_mode {
71
+ self . tokens = self . capacity . min ( self . tokens ) ;
72
+ }
66
73
self . last_window = window;
67
74
self . last_mtu = mtu;
68
75
}
@@ -90,22 +97,41 @@ impl Pacer {
90
97
91
98
let elapsed_rtts = time_elapsed. as_secs_f64 ( ) / smoothed_rtt. as_secs_f64 ( ) ;
92
99
let new_tokens = window as f64 * 1.25 * elapsed_rtts;
93
- self . tokens = self
94
- . tokens
95
- . saturating_add ( new_tokens as _ )
96
- . min ( self . capacity ) ;
97
100
101
+ self . tokens = self . tokens . saturating_add ( new_tokens as _ ) ;
102
+
103
+ let token_limit = if self . burst_mode {
104
+ self . capacity
105
+ } else {
106
+ // In slow mode, we only send one packet per interval
107
+ mtu as u64
108
+ } ;
109
+
110
+ self . tokens = self . tokens . min ( token_limit) ;
98
111
self . prev = now;
99
112
100
113
// if we can already send a packet, there is no need for delay
101
114
if self . tokens >= bytes_to_send {
102
115
return None ;
103
116
}
104
117
105
- let unscaled_delay = smoothed_rtt
106
- . checked_mul ( ( bytes_to_send. max ( self . capacity ) - self . tokens ) as _ )
107
- . unwrap_or ( Duration :: MAX )
108
- / window;
118
+ let unscaled_delay = if self . burst_mode {
119
+ smoothed_rtt
120
+ . checked_mul ( ( bytes_to_send. max ( self . capacity ) - self . tokens ) as _ )
121
+ . unwrap_or ( Duration :: MAX )
122
+ / window
123
+ } else {
124
+ // We prefer to yield at a maximum of MAX_WAIT_MS ms repetitively instead
125
+ // of waiting once
126
+ ( smoothed_rtt
127
+ . checked_mul ( ( bytes_to_send - self . tokens ) as _ )
128
+ . unwrap_or ( Duration :: MAX )
129
+ / window)
130
+ . clamp (
131
+ Duration :: from_nanos ( BURST_INTERVAL_NANOS as _ ) ,
132
+ Duration :: from_millis ( MAX_WAIT_MS ) ,
133
+ )
134
+ } ;
109
135
110
136
// divisions come before multiplications to prevent overflow
111
137
// this is the time at which the pacing window becomes empty
@@ -126,14 +152,33 @@ impl Pacer {
126
152
/// tokens for the extra-elapsed time can be stored.
127
153
///
128
154
/// Too long burst intervals make pacing less effective.
129
- fn optimal_capacity ( smoothed_rtt : Duration , window : u64 , mtu : u16 ) -> u64 {
155
+ fn optimal_capacity ( smoothed_rtt : Duration , window : u64 , mtu : u16 , burst_mode : bool ) -> u64 {
130
156
let rtt = smoothed_rtt. as_nanos ( ) . max ( 1 ) ;
131
157
132
- let capacity = ( ( window as u128 * BURST_INTERVAL_NANOS ) / rtt) as u64 ;
158
+ let mut capacity = ( ( window as u128 * BURST_INTERVAL_NANOS ) / rtt) as u64 ;
159
+
160
+ if burst_mode {
161
+ // Small bursts are less efficient (no GSO), could increase latency and don't effectively
162
+ // use the channel's buffer capacity. Large bursts might block the connection on sending.
163
+ capacity = capacity. clamp ( MIN_BURST_SIZE * mtu as u64 , MAX_BURST_SIZE * mtu as u64 ) ;
164
+ }
165
+ capacity
166
+ }
133
167
134
- // Small bursts are less efficient (no GSO), could increase latency and don't effectively
135
- // use the channel's buffer capacity. Large bursts might block the connection on sending.
136
- capacity. clamp ( MIN_BURST_SIZE * mtu as u64 , MAX_BURST_SIZE * mtu as u64 )
168
+ /// Determine if pacer must stay in burst mode (original behavior) or switch to slow mode
169
+ ///
170
+ /// On very slow network link, we must avoid sending packets by burst. This could lead to issues
171
+ /// such as packet drop. Instead, we would prefer to space each packet by the appropriate amount
172
+ /// of time.
173
+ ///
174
+ /// We determine if the pacer must switch to slow mode depending on the packet spacing computed
175
+ /// as follows:
176
+ ///
177
+ /// `packet_spacing = RTT * MTU / cwnd`
178
+ ///
179
+ /// Slow mode is detected if packet_spacing exceeds `SLOW_MODE_PACKET_SPACING_THRESHOLD_MS`
180
+ fn is_burst_mode ( smoothed_rtt : Duration , window : u64 , mtu : u16 ) -> bool {
181
+ smoothed_rtt. as_millis ( ) as u64 * mtu as u64 / window < SLOW_MODE_PACKET_SPACING_THRESHOLD_MS
137
182
}
138
183
139
184
/// The burst interval
@@ -149,6 +194,11 @@ const MIN_BURST_SIZE: u64 = 10;
149
194
150
195
/// Creating 256 packets took 1ms in a benchmark, so larger bursts don't make sense.
151
196
const MAX_BURST_SIZE : u64 = 256 ;
197
+ /// Packet spacing threshold in ms computed from the congestion window and the RTT.
198
+ /// Above this threshold, we consider the network as slow and adapt the pacer accordingly
199
+ const SLOW_MODE_PACKET_SPACING_THRESHOLD_MS : u64 = 10 ;
200
+ /// Maximum amount of time the pacer can wait while in slow mode
201
+ const MAX_WAIT_MS : u64 = 10 ;
152
202
153
203
#[ cfg( test) ]
154
204
mod tests {
0 commit comments