diff --git a/netty/src/main/java/io/grpc/netty/NettyServer.java b/netty/src/main/java/io/grpc/netty/NettyServer.java index c824be8c97..ff9ed8e368 100644 --- a/netty/src/main/java/io/grpc/netty/NettyServer.java +++ b/netty/src/main/java/io/grpc/netty/NettyServer.java @@ -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 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) { diff --git a/netty/src/main/java/io/grpc/netty/NettyServerBuilder.java b/netty/src/main/java/io/grpc/netty/NettyServerBuilder.java index 3db61b3194..0b724b062a 100644 --- a/netty/src/main/java/io/grpc/netty/NettyServerBuilder.java +++ b/netty/src/main/java/io/grpc/netty/NettyServerBuilder.java @@ -81,6 +81,8 @@ public final class NettyServerBuilder extends AbstractServerImplBuilderEven 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 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 diff --git a/netty/src/main/java/io/grpc/netty/NettyServerTransport.java b/netty/src/main/java/io/grpc/netty/NettyServerTransport.java index 753cc582e0..18ab2d5c8b 100644 --- a/netty/src/main/java/io/grpc/netty/NettyServerTransport.java +++ b/netty/src/main/java/io/grpc/netty/NettyServerTransport.java @@ -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); } } diff --git a/netty/src/test/java/io/grpc/netty/NettyClientTransportTest.java b/netty/src/test/java/io/grpc/netty/NettyClientTransportTest.java index 4668c09ec4..343c3df624 100644 --- a/netty/src/test/java/io/grpc/netty/NettyClientTransportTest.java +++ b/netty/src/test/java/io/grpc/netty/NettyClientTransportTest.java @@ -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()); diff --git a/netty/src/test/java/io/grpc/netty/NettyServerTest.java b/netty/src/test/java/io/grpc/netty/NettyServerTest.java index f8f8eb0fa7..5328991dfd 100644 --- a/netty/src/test/java/io/grpc/netty/NettyServerTest.java +++ b/netty/src/test/java/io/grpc/netty/NettyServerTest.java @@ -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); }