netty: Add config for server keepalive enforcement

Now that there is a config, the new defaults are now being enabled.
Previously there were no default limits. Now keepalives may not be more
frequent than every 5 minutes and only when there are outstanding RPCs.
This commit is contained in:
Eric Anderson 2017-04-06 15:48:33 -07:00 committed by GitHub
parent ebd2f2d2f7
commit 4236027713
6 changed files with 80 additions and 10 deletions

View File

@ -77,13 +77,16 @@ class NettyServer implements InternalServer {
private final int maxHeaderListSize;
private final long keepAliveTimeInNanos;
private final long keepAliveTimeoutInNanos;
private final boolean permitKeepAliveWithoutCalls;
private final long permitKeepAliveTimeInNanos;
private final ReferenceCounted eventLoopReferenceCounter = new EventLoopReferenceCounter();
NettyServer(SocketAddress address, Class<? extends ServerChannel> channelType,
@Nullable EventLoopGroup bossGroup, @Nullable EventLoopGroup workerGroup,
ProtocolNegotiator protocolNegotiator, int maxStreamsPerConnection,
int flowControlWindow, int maxMessageSize, int maxHeaderListSize,
long keepAliveTimeInNanos, long keepAliveTimeoutInNanos) {
long keepAliveTimeInNanos, long keepAliveTimeoutInNanos,
boolean permitKeepAliveWithoutCalls, long permitKeepAliveTimeInNanos) {
this.address = address;
this.channelType = checkNotNull(channelType, "channelType");
this.bossGroup = bossGroup;
@ -97,6 +100,8 @@ class NettyServer implements InternalServer {
this.maxHeaderListSize = maxHeaderListSize;
this.keepAliveTimeInNanos = keepAliveTimeInNanos;
this.keepAliveTimeoutInNanos = keepAliveTimeoutInNanos;
this.permitKeepAliveWithoutCalls = permitKeepAliveWithoutCalls;
this.permitKeepAliveTimeInNanos = permitKeepAliveTimeInNanos;
}
@Override
@ -130,7 +135,8 @@ class NettyServer implements InternalServer {
public void initChannel(Channel ch) throws Exception {
NettyServerTransport transport = new NettyServerTransport(ch, protocolNegotiator,
maxStreamsPerConnection, flowControlWindow, maxMessageSize, maxHeaderListSize,
keepAliveTimeInNanos, keepAliveTimeoutInNanos);
keepAliveTimeInNanos, keepAliveTimeoutInNanos, permitKeepAliveWithoutCalls,
permitKeepAliveTimeInNanos);
ServerTransportListener transportListener;
// This is to order callbacks on the listener, not to guard access to channel.
synchronized (NettyServer.this) {

View File

@ -81,6 +81,8 @@ public final class NettyServerBuilder extends AbstractServerImplBuilder<NettySer
private int maxHeaderListSize = GrpcUtil.DEFAULT_MAX_HEADER_LIST_SIZE;
private long keepAliveTimeInNanos = DEFAULT_SERVER_KEEPALIVE_TIME_NANOS;
private long keepAliveTimeoutInNanos = DEFAULT_SERVER_KEEPALIVE_TIMEOUT_NANOS;
private boolean permitKeepAliveWithoutCalls;
private long permitKeepAliveTimeInNanos = TimeUnit.MINUTES.toNanos(5);
/**
* Creates a server builder that will bind to the given port.
@ -274,6 +276,54 @@ public final class NettyServerBuilder extends AbstractServerImplBuilder<NettySer
return this;
}
/**
* Specify the most aggressive keep-alive time clients are permitted to configure. The server will
* try to detect clients exceeding this rate and when detected will forcefully close the
* connection. The default is 5 minutes.
*
* <p>Even though a default is defined that allows some keep-alives, clients must not use
* keep-alive without approval from the service owner. Otherwise, they may experience failures in
* the future if the service becomes more restrictive. When unthrottled, keep-alives can cause a
* significant amount of traffic and CPU usage, so clients and servers should be conservative in
* what they use and accept.
*
* @see #denyKeepAliveWithoutCalls()
* @see #permitKeepAliveWithoutCalls()
* @since 1.3.0
*/
public NettyServerBuilder permitKeepAliveTime(long keepAliveTime, TimeUnit timeUnit) {
checkArgument(keepAliveTime >= 0, "permit keepalive time must be non-negative");
permitKeepAliveTimeInNanos = timeUnit.toNanos(keepAliveTime);
return this;
}
/**
* Allow clients to send keep-alive HTTP/2 PINGs even if there are no outstanding RPCs on the
* connection.
*
* @see #denyKeepAliveWithoutCalls()
* @see #permitKeepAliveTime(long, TimeUnit)
* @since 1.3.0
*/
public NettyServerBuilder permitKeepAliveWithoutCalls() {
permitKeepAliveWithoutCalls = true;
return this;
}
/**
* Only allow clients to send keep-alive HTTP/2 PINGs when there are outstanding RPCs on the
* connection. This reduces the resources idle connections may consume, reducing the impact of
* permitting keep-alive. This is the default.
*
* @see #permitKeepAliveWithoutCalls()
* @see #permitKeepAliveTime(long, TimeUnit)
* @since 1.3.0
*/
public NettyServerBuilder denyKeepAliveWithoutCalls() {
permitKeepAliveWithoutCalls = false;
return this;
}
@Override
@CheckReturnValue
protected NettyServer buildTransportServer() {
@ -284,7 +334,8 @@ public final class NettyServerBuilder extends AbstractServerImplBuilder<NettySer
}
return new NettyServer(address, channelType, bossEventLoopGroup, workerEventLoopGroup,
negotiator, maxConcurrentCallsPerConnection, flowControlWindow, maxMessageSize,
maxHeaderListSize, keepAliveTimeInNanos, keepAliveTimeoutInNanos);
maxHeaderListSize, keepAliveTimeInNanos, keepAliveTimeoutInNanos,
permitKeepAliveWithoutCalls, permitKeepAliveTimeInNanos);
}
@Override

View File

@ -118,7 +118,9 @@ class NettyServerHandler extends AbstractNettyHandler {
int maxHeaderListSize,
int maxMessageSize,
long keepAliveTimeInNanos,
long keepAliveTimeoutInNanos) {
long keepAliveTimeoutInNanos,
boolean permitKeepAliveWithoutCalls,
long permitKeepAliveTimeInNanos) {
Preconditions.checkArgument(maxHeaderListSize > 0, "maxHeaderListSize must be positive");
Http2FrameLogger frameLogger = new Http2FrameLogger(LogLevel.DEBUG, NettyServerHandler.class);
Http2HeadersDecoder headersDecoder = new GrpcHttp2ServerHeadersDecoder(maxHeaderListSize);
@ -127,7 +129,8 @@ class NettyServerHandler extends AbstractNettyHandler {
Http2FrameWriter frameWriter =
new Http2OutboundFrameLogger(new DefaultHttp2FrameWriter(), frameLogger);
return newHandler(frameReader, frameWriter, transportListener, maxStreams, flowControlWindow,
maxHeaderListSize, maxMessageSize, keepAliveTimeInNanos, keepAliveTimeoutInNanos, true, 0);
maxHeaderListSize, maxMessageSize, keepAliveTimeInNanos, keepAliveTimeoutInNanos,
permitKeepAliveWithoutCalls, permitKeepAliveTimeInNanos);
}
@VisibleForTesting

View File

@ -60,10 +60,13 @@ class NettyServerTransport implements ServerTransport {
private final int maxHeaderListSize;
private final long keepAliveTimeInNanos;
private final long keepAliveTimeoutInNanos;
private final boolean permitKeepAliveWithoutCalls;
private final long permitKeepAliveTimeInNanos;
NettyServerTransport(Channel channel, ProtocolNegotiator protocolNegotiator, int maxStreams,
int flowControlWindow, int maxMessageSize, int maxHeaderListSize, long keepAliveTimeInNanos,
long keepAliveTimeoutInNanos) {
long keepAliveTimeoutInNanos, boolean permitKeepAliveWithoutCalls,
long permitKeepAliveTimeInNanos) {
this.channel = Preconditions.checkNotNull(channel, "channel");
this.protocolNegotiator = Preconditions.checkNotNull(protocolNegotiator, "protocolNegotiator");
this.maxStreams = maxStreams;
@ -72,6 +75,8 @@ class NettyServerTransport implements ServerTransport {
this.maxHeaderListSize = maxHeaderListSize;
this.keepAliveTimeInNanos = keepAliveTimeInNanos;
this.keepAliveTimeoutInNanos = keepAliveTimeoutInNanos;
this.permitKeepAliveWithoutCalls = permitKeepAliveWithoutCalls;
this.permitKeepAliveTimeInNanos = permitKeepAliveTimeInNanos;
}
public void start(ServerTransportListener listener) {
@ -135,6 +140,7 @@ class NettyServerTransport implements ServerTransport {
*/
private NettyServerHandler createHandler(ServerTransportListener transportListener) {
return NettyServerHandler.newHandler(transportListener, maxStreams, flowControlWindow,
maxHeaderListSize, maxMessageSize, keepAliveTimeInNanos, keepAliveTimeoutInNanos);
maxHeaderListSize, maxMessageSize, keepAliveTimeInNanos, keepAliveTimeoutInNanos,
permitKeepAliveWithoutCalls, permitKeepAliveTimeInNanos);
}
}

View File

@ -481,7 +481,7 @@ public class NettyClientTransportTest {
server = new NettyServer(TestUtils.testServerAddress(0),
NioServerSocketChannel.class, group, group, negotiator,
maxStreamsPerConnection, DEFAULT_WINDOW_SIZE, DEFAULT_MAX_MESSAGE_SIZE, maxHeaderListSize,
DEFAULT_SERVER_KEEPALIVE_TIME_NANOS, DEFAULT_SERVER_KEEPALIVE_TIMEOUT_NANOS);
DEFAULT_SERVER_KEEPALIVE_TIME_NANOS, DEFAULT_SERVER_KEEPALIVE_TIMEOUT_NANOS, true, 0);
server.start(serverListener);
address = TestUtils.testServerAddress(server.getPort());
authority = GrpcUtil.authorityFromHostAndPort(address.getHostString(), address.getPort());

View File

@ -59,7 +59,9 @@ public class NettyServerTest {
1, // ignore
1, // ignore
1, // ignore
1); // ignore
1, // ignore
true, // ignore
0); // ignore
ns.start(new ServerListener() {
@Override
public ServerTransportListener transportCreated(ServerTransport transport) {
@ -91,7 +93,9 @@ public class NettyServerTest {
1, // ignore
1, // ignore
1, // ignore
1); // ignore
1, // ignore
true, // ignore
0); // ignore
assertThat(ns.getPort()).isEqualTo(-1);
}