16
16
17
17
package io .grpc .netty ;
18
18
19
+ import static com .google .common .base .Preconditions .checkNotNull ;
19
20
import static io .netty .handler .codec .http2 .Http2CodecUtil .getEmbeddedHttp2Exception ;
20
21
21
22
import com .google .common .annotations .VisibleForTesting ;
22
23
import com .google .common .base .Preconditions ;
24
+ import com .google .common .base .Ticker ;
23
25
import io .grpc .ChannelLogger ;
24
26
import io .netty .channel .ChannelHandlerContext ;
25
27
import io .netty .channel .ChannelPromise ;
@@ -44,6 +46,7 @@ abstract class AbstractNettyHandler extends GrpcHttp2ConnectionHandler {
44
46
private boolean autoTuneFlowControlOn ;
45
47
private ChannelHandlerContext ctx ;
46
48
private boolean initialWindowSent = false ;
49
+ private final Ticker ticker ;
47
50
48
51
private static final long BDP_MEASUREMENT_PING = 1234 ;
49
52
@@ -54,20 +57,22 @@ abstract class AbstractNettyHandler extends GrpcHttp2ConnectionHandler {
54
57
Http2Settings initialSettings ,
55
58
ChannelLogger negotiationLogger ,
56
59
boolean autoFlowControl ,
57
- PingLimiter pingLimiter ) {
60
+ PingLimiter pingLimiter ,
61
+ Ticker ticker ) {
58
62
super (channelUnused , decoder , encoder , initialSettings , negotiationLogger );
59
63
60
64
// During a graceful shutdown, wait until all streams are closed.
61
65
gracefulShutdownTimeoutMillis (GRACEFUL_SHUTDOWN_NO_TIMEOUT );
62
66
63
67
// Extract the connection window from the settings if it was set.
64
68
this .initialConnectionWindow = initialSettings .initialWindowSize () == null ? -1 :
65
- initialSettings .initialWindowSize ();
69
+ initialSettings .initialWindowSize ();
66
70
this .autoTuneFlowControlOn = autoFlowControl ;
67
71
if (pingLimiter == null ) {
68
72
pingLimiter = new AllowPingLimiter ();
69
73
}
70
74
this .flowControlPing = new FlowControlPinger (pingLimiter );
75
+ this .ticker = checkNotNull (ticker , "ticker" );
71
76
}
72
77
73
78
@ Override
@@ -131,14 +136,17 @@ void setAutoTuneFlowControl(boolean isOn) {
131
136
final class FlowControlPinger {
132
137
133
138
private static final int MAX_WINDOW_SIZE = 8 * 1024 * 1024 ;
139
+ public static final int MAX_BACKOFF = 10 ;
134
140
135
141
private final PingLimiter pingLimiter ;
136
142
private int pingCount ;
137
143
private int pingReturn ;
138
144
private boolean pinging ;
139
145
private int dataSizeSincePing ;
140
- private float lastBandwidth ; // bytes per second
146
+ private long lastBandwidth ; // bytes per nanosecond
141
147
private long lastPingTime ;
148
+ private int lastTargetWindow ;
149
+ private int pingFrequencyMultiplier ;
142
150
143
151
public FlowControlPinger (PingLimiter pingLimiter ) {
144
152
Preconditions .checkNotNull (pingLimiter , "pingLimiter" );
@@ -157,10 +165,24 @@ public void onDataRead(int dataLength, int paddingLength) {
157
165
if (!autoTuneFlowControlOn ) {
158
166
return ;
159
167
}
160
- if (!isPinging () && pingLimiter .isPingAllowed ()) {
168
+
169
+ // Note that we are double counting around the ping initiation as the current data will be
170
+ // added at the end of this method, so will be available in the next check. This at worst
171
+ // causes us to send a ping one data packet earlier, but makes startup faster if there are
172
+ // small packets before big ones.
173
+ int dataForCheck = getDataSincePing () + dataLength + paddingLength ;
174
+ // Need to double the data here to account for targetWindow being set to twice the data below
175
+ if (!isPinging () && pingLimiter .isPingAllowed ()
176
+ && dataForCheck * 2 >= lastTargetWindow * pingFrequencyMultiplier ) {
161
177
setPinging (true );
162
178
sendPing (ctx ());
163
179
}
180
+
181
+ if (lastTargetWindow == 0 ) {
182
+ lastTargetWindow =
183
+ decoder ().flowController ().initialWindowSize (connection ().connectionStream ());
184
+ }
185
+
164
186
incrementDataSincePing (dataLength + paddingLength );
165
187
}
166
188
@@ -169,25 +191,32 @@ public void updateWindow() throws Http2Exception {
169
191
return ;
170
192
}
171
193
pingReturn ++;
172
- long elapsedTime = (System .nanoTime () - lastPingTime );
194
+ setPinging (false );
195
+
196
+ long elapsedTime = (ticker .read () - lastPingTime );
173
197
if (elapsedTime == 0 ) {
174
198
elapsedTime = 1 ;
175
199
}
200
+
176
201
long bandwidth = (getDataSincePing () * TimeUnit .SECONDS .toNanos (1 )) / elapsedTime ;
177
- Http2LocalFlowController fc = decoder ().flowController ();
178
202
// Calculate new window size by doubling the observed BDP, but cap at max window
179
203
int targetWindow = Math .min (getDataSincePing () * 2 , MAX_WINDOW_SIZE );
180
- setPinging ( false );
204
+ Http2LocalFlowController fc = decoder (). flowController ( );
181
205
int currentWindow = fc .initialWindowSize (connection ().connectionStream ());
182
- if (targetWindow > currentWindow && bandwidth > lastBandwidth ) {
183
- lastBandwidth = bandwidth ;
184
- int increase = targetWindow - currentWindow ;
185
- fc .incrementWindowSize (connection ().connectionStream (), increase );
186
- fc .initialWindowSize (targetWindow );
187
- Http2Settings settings = new Http2Settings ();
188
- settings .initialWindowSize (targetWindow );
189
- frameWriter ().writeSettings (ctx (), settings , ctx ().newPromise ());
206
+ if (bandwidth <= lastBandwidth || targetWindow <= currentWindow ) {
207
+ pingFrequencyMultiplier = Math .min (pingFrequencyMultiplier + 1 , MAX_BACKOFF );
208
+ return ;
190
209
}
210
+
211
+ pingFrequencyMultiplier = 0 ; // react quickly when size is changing
212
+ lastBandwidth = bandwidth ;
213
+ lastTargetWindow = targetWindow ;
214
+ int increase = targetWindow - currentWindow ;
215
+ fc .incrementWindowSize (connection ().connectionStream (), increase );
216
+ fc .initialWindowSize (targetWindow );
217
+ Http2Settings settings = new Http2Settings ();
218
+ settings .initialWindowSize (targetWindow );
219
+ frameWriter ().writeSettings (ctx (), settings , ctx ().newPromise ());
191
220
}
192
221
193
222
private boolean isPinging () {
@@ -200,7 +229,7 @@ private void setPinging(boolean pingOut) {
200
229
201
230
private void sendPing (ChannelHandlerContext ctx ) {
202
231
setDataSizeSincePing (0 );
203
- lastPingTime = System . nanoTime ();
232
+ lastPingTime = ticker . read ();
204
233
encoder ().writePing (ctx , false , BDP_MEASUREMENT_PING , ctx .newPromise ());
205
234
pingCount ++;
206
235
}
@@ -229,10 +258,12 @@ private void setDataSizeSincePing(int dataSize) {
229
258
dataSizeSincePing = dataSize ;
230
259
}
231
260
261
+ // Only used in testing
232
262
@ VisibleForTesting
233
263
void setDataSizeAndSincePing (int dataSize ) {
234
264
setDataSizeSincePing (dataSize );
235
- lastPingTime = System .nanoTime () - TimeUnit .SECONDS .toNanos (1 );
265
+ pingFrequencyMultiplier = 1 ;
266
+ lastPingTime = ticker .read () ;
236
267
}
237
268
}
238
269
0 commit comments