Skip to content

Commit 3cbd948

Browse files
authored
Restore "netty:Auto adjust BDP ping frequency" with fix (#9847)
* Revert "Revert "netty:Auto adjust BDP ping frequency (#9650)" (#9821)" This reverts commit a2bbe84. * Eliminate half RTT delay in sending BDP Pings when starting up. * Eliminate delay for bdp ping when current read would push us over the threshold.
1 parent ecc7cf3 commit 3cbd948

9 files changed

+296
-86
lines changed

netty/src/main/java/io/grpc/netty/AbstractNettyHandler.java

Lines changed: 48 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,12 @@
1616

1717
package io.grpc.netty;
1818

19+
import static com.google.common.base.Preconditions.checkNotNull;
1920
import static io.netty.handler.codec.http2.Http2CodecUtil.getEmbeddedHttp2Exception;
2021

2122
import com.google.common.annotations.VisibleForTesting;
2223
import com.google.common.base.Preconditions;
24+
import com.google.common.base.Ticker;
2325
import io.grpc.ChannelLogger;
2426
import io.netty.channel.ChannelHandlerContext;
2527
import io.netty.channel.ChannelPromise;
@@ -44,6 +46,7 @@ abstract class AbstractNettyHandler extends GrpcHttp2ConnectionHandler {
4446
private boolean autoTuneFlowControlOn;
4547
private ChannelHandlerContext ctx;
4648
private boolean initialWindowSent = false;
49+
private final Ticker ticker;
4750

4851
private static final long BDP_MEASUREMENT_PING = 1234;
4952

@@ -54,20 +57,22 @@ abstract class AbstractNettyHandler extends GrpcHttp2ConnectionHandler {
5457
Http2Settings initialSettings,
5558
ChannelLogger negotiationLogger,
5659
boolean autoFlowControl,
57-
PingLimiter pingLimiter) {
60+
PingLimiter pingLimiter,
61+
Ticker ticker) {
5862
super(channelUnused, decoder, encoder, initialSettings, negotiationLogger);
5963

6064
// During a graceful shutdown, wait until all streams are closed.
6165
gracefulShutdownTimeoutMillis(GRACEFUL_SHUTDOWN_NO_TIMEOUT);
6266

6367
// Extract the connection window from the settings if it was set.
6468
this.initialConnectionWindow = initialSettings.initialWindowSize() == null ? -1 :
65-
initialSettings.initialWindowSize();
69+
initialSettings.initialWindowSize();
6670
this.autoTuneFlowControlOn = autoFlowControl;
6771
if (pingLimiter == null) {
6872
pingLimiter = new AllowPingLimiter();
6973
}
7074
this.flowControlPing = new FlowControlPinger(pingLimiter);
75+
this.ticker = checkNotNull(ticker, "ticker");
7176
}
7277

7378
@Override
@@ -131,14 +136,17 @@ void setAutoTuneFlowControl(boolean isOn) {
131136
final class FlowControlPinger {
132137

133138
private static final int MAX_WINDOW_SIZE = 8 * 1024 * 1024;
139+
public static final int MAX_BACKOFF = 10;
134140

135141
private final PingLimiter pingLimiter;
136142
private int pingCount;
137143
private int pingReturn;
138144
private boolean pinging;
139145
private int dataSizeSincePing;
140-
private float lastBandwidth; // bytes per second
146+
private long lastBandwidth; // bytes per nanosecond
141147
private long lastPingTime;
148+
private int lastTargetWindow;
149+
private int pingFrequencyMultiplier;
142150

143151
public FlowControlPinger(PingLimiter pingLimiter) {
144152
Preconditions.checkNotNull(pingLimiter, "pingLimiter");
@@ -157,10 +165,24 @@ public void onDataRead(int dataLength, int paddingLength) {
157165
if (!autoTuneFlowControlOn) {
158166
return;
159167
}
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) {
161177
setPinging(true);
162178
sendPing(ctx());
163179
}
180+
181+
if (lastTargetWindow == 0) {
182+
lastTargetWindow =
183+
decoder().flowController().initialWindowSize(connection().connectionStream());
184+
}
185+
164186
incrementDataSincePing(dataLength + paddingLength);
165187
}
166188

@@ -169,25 +191,32 @@ public void updateWindow() throws Http2Exception {
169191
return;
170192
}
171193
pingReturn++;
172-
long elapsedTime = (System.nanoTime() - lastPingTime);
194+
setPinging(false);
195+
196+
long elapsedTime = (ticker.read() - lastPingTime);
173197
if (elapsedTime == 0) {
174198
elapsedTime = 1;
175199
}
200+
176201
long bandwidth = (getDataSincePing() * TimeUnit.SECONDS.toNanos(1)) / elapsedTime;
177-
Http2LocalFlowController fc = decoder().flowController();
178202
// Calculate new window size by doubling the observed BDP, but cap at max window
179203
int targetWindow = Math.min(getDataSincePing() * 2, MAX_WINDOW_SIZE);
180-
setPinging(false);
204+
Http2LocalFlowController fc = decoder().flowController();
181205
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;
190209
}
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());
191220
}
192221

193222
private boolean isPinging() {
@@ -200,7 +229,7 @@ private void setPinging(boolean pingOut) {
200229

201230
private void sendPing(ChannelHandlerContext ctx) {
202231
setDataSizeSincePing(0);
203-
lastPingTime = System.nanoTime();
232+
lastPingTime = ticker.read();
204233
encoder().writePing(ctx, false, BDP_MEASUREMENT_PING, ctx.newPromise());
205234
pingCount++;
206235
}
@@ -229,10 +258,12 @@ private void setDataSizeSincePing(int dataSize) {
229258
dataSizeSincePing = dataSize;
230259
}
231260

261+
// Only used in testing
232262
@VisibleForTesting
233263
void setDataSizeAndSincePing(int dataSize) {
234264
setDataSizeSincePing(dataSize);
235-
lastPingTime = System.nanoTime() - TimeUnit.SECONDS.toNanos(1);
265+
pingFrequencyMultiplier = 1;
266+
lastPingTime = ticker.read() ;
236267
}
237268
}
238269

netty/src/main/java/io/grpc/netty/NettyChannelBuilder.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import static io.grpc.internal.GrpcUtil.KEEPALIVE_TIME_NANOS_DISABLED;
2424

2525
import com.google.common.annotations.VisibleForTesting;
26+
import com.google.common.base.Ticker;
2627
import com.google.errorprone.annotations.CanIgnoreReturnValue;
2728
import com.google.errorprone.annotations.CheckReturnValue;
2829
import com.google.errorprone.annotations.InlineMe;
@@ -738,7 +739,7 @@ public void run() {
738739
maxMessageSize, maxHeaderListSize, keepAliveTimeNanosState.get(), keepAliveTimeoutNanos,
739740
keepAliveWithoutCalls, options.getAuthority(), options.getUserAgent(),
740741
tooManyPingsRunnable, transportTracerFactory.create(), options.getEagAttributes(),
741-
localSocketPicker, channelLogger, useGetForSafeMethods);
742+
localSocketPicker, channelLogger, useGetForSafeMethods, Ticker.systemTicker());
742743
return transport;
743744
}
744745

netty/src/main/java/io/grpc/netty/NettyClientHandler.java

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
import com.google.common.base.Preconditions;
2525
import com.google.common.base.Stopwatch;
2626
import com.google.common.base.Supplier;
27+
import com.google.common.base.Ticker;
2728
import io.grpc.Attributes;
2829
import io.grpc.ChannelLogger;
2930
import io.grpc.InternalChannelz;
@@ -143,7 +144,8 @@ static NettyClientHandler newHandler(
143144
TransportTracer transportTracer,
144145
Attributes eagAttributes,
145146
String authority,
146-
ChannelLogger negotiationLogger) {
147+
ChannelLogger negotiationLogger,
148+
Ticker ticker) {
147149
Preconditions.checkArgument(maxHeaderListSize > 0, "maxHeaderListSize must be positive");
148150
Http2HeadersDecoder headersDecoder = new GrpcHttp2ClientHeadersDecoder(maxHeaderListSize);
149151
Http2FrameReader frameReader = new DefaultHttp2FrameReader(headersDecoder);
@@ -169,7 +171,8 @@ static NettyClientHandler newHandler(
169171
transportTracer,
170172
eagAttributes,
171173
authority,
172-
negotiationLogger);
174+
negotiationLogger,
175+
ticker);
173176
}
174177

175178
@VisibleForTesting
@@ -187,7 +190,8 @@ static NettyClientHandler newHandler(
187190
TransportTracer transportTracer,
188191
Attributes eagAttributes,
189192
String authority,
190-
ChannelLogger negotiationLogger) {
193+
ChannelLogger negotiationLogger,
194+
Ticker ticker) {
191195
Preconditions.checkNotNull(connection, "connection");
192196
Preconditions.checkNotNull(frameReader, "frameReader");
193197
Preconditions.checkNotNull(lifecycleManager, "lifecycleManager");
@@ -237,7 +241,8 @@ static NettyClientHandler newHandler(
237241
eagAttributes,
238242
authority,
239243
autoFlowControl,
240-
pingCounter);
244+
pingCounter,
245+
ticker);
241246
}
242247

243248
private NettyClientHandler(
@@ -253,9 +258,10 @@ private NettyClientHandler(
253258
Attributes eagAttributes,
254259
String authority,
255260
boolean autoFlowControl,
256-
PingLimiter pingLimiter) {
261+
PingLimiter pingLimiter,
262+
Ticker ticker) {
257263
super(/* channelUnused= */ null, decoder, encoder, settings,
258-
negotiationLogger, autoFlowControl, pingLimiter);
264+
negotiationLogger, autoFlowControl, pingLimiter, ticker);
259265
this.lifecycleManager = lifecycleManager;
260266
this.keepAliveManager = keepAliveManager;
261267
this.stopwatchFactory = stopwatchFactory;

netty/src/main/java/io/grpc/netty/NettyClientTransport.java

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import com.google.common.annotations.VisibleForTesting;
2424
import com.google.common.base.MoreObjects;
2525
import com.google.common.base.Preconditions;
26+
import com.google.common.base.Ticker;
2627
import com.google.common.util.concurrent.ListenableFuture;
2728
import com.google.common.util.concurrent.SettableFuture;
2829
import io.grpc.Attributes;
@@ -102,6 +103,7 @@ class NettyClientTransport implements ConnectionClientTransport {
102103
private final LocalSocketPicker localSocketPicker;
103104
private final ChannelLogger channelLogger;
104105
private final boolean useGetForSafeMethods;
106+
private final Ticker ticker;
105107

106108
NettyClientTransport(
107109
SocketAddress address, ChannelFactory<? extends Channel> channelFactory,
@@ -112,7 +114,8 @@ class NettyClientTransport implements ConnectionClientTransport {
112114
boolean keepAliveWithoutCalls, String authority, @Nullable String userAgent,
113115
Runnable tooManyPingsRunnable, TransportTracer transportTracer, Attributes eagAttributes,
114116
LocalSocketPicker localSocketPicker, ChannelLogger channelLogger,
115-
boolean useGetForSafeMethods) {
117+
boolean useGetForSafeMethods, Ticker ticker) {
118+
116119
this.negotiator = Preconditions.checkNotNull(negotiator, "negotiator");
117120
this.negotiationScheme = this.negotiator.scheme();
118121
this.remoteAddress = Preconditions.checkNotNull(address, "address");
@@ -137,6 +140,7 @@ class NettyClientTransport implements ConnectionClientTransport {
137140
this.logId = InternalLogId.allocate(getClass(), remoteAddress.toString());
138141
this.channelLogger = Preconditions.checkNotNull(channelLogger, "channelLogger");
139142
this.useGetForSafeMethods = useGetForSafeMethods;
143+
this.ticker = Preconditions.checkNotNull(ticker, "ticker");
140144
}
141145

142146
@Override
@@ -225,7 +229,8 @@ public Runnable start(Listener transportListener) {
225229
transportTracer,
226230
eagAttributes,
227231
authorityString,
228-
channelLogger);
232+
channelLogger,
233+
ticker);
229234

230235
ChannelHandler negotiationHandler = negotiator.newHandler(handler);
231236

netty/src/main/java/io/grpc/netty/NettyServerHandler.java

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
import com.google.common.annotations.VisibleForTesting;
3535
import com.google.common.base.Preconditions;
3636
import com.google.common.base.Strings;
37+
import com.google.common.base.Ticker;
3738
import io.grpc.Attributes;
3839
import io.grpc.ChannelLogger;
3940
import io.grpc.ChannelLogger.ChannelLogLevel;
@@ -190,7 +191,8 @@ static NettyServerHandler newHandler(
190191
maxConnectionAgeGraceInNanos,
191192
permitKeepAliveWithoutCalls,
192193
permitKeepAliveTimeInNanos,
193-
eagAttributes);
194+
eagAttributes,
195+
Ticker.systemTicker());
194196
}
195197

196198
static NettyServerHandler newHandler(
@@ -212,7 +214,8 @@ static NettyServerHandler newHandler(
212214
long maxConnectionAgeGraceInNanos,
213215
boolean permitKeepAliveWithoutCalls,
214216
long permitKeepAliveTimeInNanos,
215-
Attributes eagAttributes) {
217+
Attributes eagAttributes,
218+
Ticker ticker) {
216219
Preconditions.checkArgument(maxStreams > 0, "maxStreams must be positive: %s", maxStreams);
217220
Preconditions.checkArgument(flowControlWindow > 0, "flowControlWindow must be positive: %s",
218221
flowControlWindow);
@@ -245,6 +248,10 @@ static NettyServerHandler newHandler(
245248
settings.maxConcurrentStreams(maxStreams);
246249
settings.maxHeaderListSize(maxHeaderListSize);
247250

251+
if (ticker == null) {
252+
ticker = Ticker.systemTicker();
253+
}
254+
248255
return new NettyServerHandler(
249256
channelUnused,
250257
connection,
@@ -258,7 +265,7 @@ static NettyServerHandler newHandler(
258265
maxConnectionAgeInNanos, maxConnectionAgeGraceInNanos,
259266
keepAliveEnforcer,
260267
autoFlowControl,
261-
eagAttributes);
268+
eagAttributes, ticker);
262269
}
263270

264271
private NettyServerHandler(
@@ -278,9 +285,10 @@ private NettyServerHandler(
278285
long maxConnectionAgeGraceInNanos,
279286
final KeepAliveEnforcer keepAliveEnforcer,
280287
boolean autoFlowControl,
281-
Attributes eagAttributes) {
288+
Attributes eagAttributes,
289+
Ticker ticker) {
282290
super(channelUnused, decoder, encoder, settings, new ServerChannelLogger(),
283-
autoFlowControl, null);
291+
autoFlowControl, null, ticker);
284292

285293
final MaxConnectionIdleManager maxConnectionIdleManager;
286294
if (maxConnectionIdleInNanos == MAX_CONNECTION_IDLE_NANOS_DISABLED) {
@@ -325,7 +333,6 @@ public void onStreamClosed(Http2Stream stream) {
325333
this.transportListener = checkNotNull(transportListener, "transportListener");
326334
this.streamTracerFactories = checkNotNull(streamTracerFactories, "streamTracerFactories");
327335
this.transportTracer = checkNotNull(transportTracer, "transportTracer");
328-
329336
// Set the frame listener on the decoder.
330337
decoder().frameListener(new FrameListener());
331338
}

0 commit comments

Comments
 (0)