netty: disable huffman coding in headers (#10563)

Huffman in the datacenter doesn't add much value in the common cases.  It could be useful to turn on huffman based on the connection latency (say, >10ms means "assume cross-datacenter") but the Netty API doesn't lend it to that. The savings here aren't huge and it is expensive; the table provides the biggest savings.
This commit is contained in:
Benjamin Fedorka 2023-11-14 14:09:38 -05:00 committed by GitHub
parent 90e76a1b4a
commit 2b65e660c0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 50 additions and 1 deletions

View File

@ -54,6 +54,7 @@ import io.netty.handler.codec.http2.DefaultHttp2ConnectionDecoder;
import io.netty.handler.codec.http2.DefaultHttp2ConnectionEncoder;
import io.netty.handler.codec.http2.DefaultHttp2FrameReader;
import io.netty.handler.codec.http2.DefaultHttp2FrameWriter;
import io.netty.handler.codec.http2.DefaultHttp2HeadersEncoder;
import io.netty.handler.codec.http2.DefaultHttp2LocalFlowController;
import io.netty.handler.codec.http2.DefaultHttp2RemoteFlowController;
import io.netty.handler.codec.http2.Http2CodecUtil;
@ -69,6 +70,7 @@ import io.netty.handler.codec.http2.Http2FrameReader;
import io.netty.handler.codec.http2.Http2FrameWriter;
import io.netty.handler.codec.http2.Http2Headers;
import io.netty.handler.codec.http2.Http2HeadersDecoder;
import io.netty.handler.codec.http2.Http2HeadersEncoder;
import io.netty.handler.codec.http2.Http2InboundFrameLogger;
import io.netty.handler.codec.http2.Http2OutboundFrameLogger;
import io.netty.handler.codec.http2.Http2Settings;
@ -150,7 +152,9 @@ class NettyClientHandler extends AbstractNettyHandler {
Preconditions.checkArgument(maxHeaderListSize > 0, "maxHeaderListSize must be positive");
Http2HeadersDecoder headersDecoder = new GrpcHttp2ClientHeadersDecoder(maxHeaderListSize);
Http2FrameReader frameReader = new DefaultHttp2FrameReader(headersDecoder);
Http2FrameWriter frameWriter = new DefaultHttp2FrameWriter();
Http2HeadersEncoder encoder = new DefaultHttp2HeadersEncoder(
Http2HeadersEncoder.NEVER_SENSITIVE, false, 16, Integer.MAX_VALUE);
Http2FrameWriter frameWriter = new DefaultHttp2FrameWriter(encoder);
Http2Connection connection = new DefaultHttp2Connection(false);
WeightedFairQueueByteDistributor dist = new WeightedFairQueueByteDistributor(connection);
dist.allocationQuantum(16 * 1024); // Make benchmarks fast again.

View File

@ -36,6 +36,7 @@ import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import com.google.common.base.Strings;
import com.google.common.base.Ticker;
import com.google.common.io.ByteStreams;
import com.google.common.util.concurrent.SettableFuture;
@ -69,6 +70,7 @@ import io.grpc.internal.testing.TestUtils;
import io.grpc.netty.NettyChannelBuilder.LocalSocketPicker;
import io.grpc.netty.NettyTestUtil.TrackingObjectPoolForTest;
import io.grpc.testing.TlsTesting;
import io.netty.buffer.ByteBuf;
import io.netty.channel.Channel;
import io.netty.channel.ChannelConfig;
import io.netty.channel.ChannelDuplexHandler;
@ -76,6 +78,7 @@ import io.netty.channel.ChannelFactory;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelOption;
import io.netty.channel.ChannelPromise;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.ReflectiveChannelFactory;
import io.netty.channel.local.LocalChannel;
@ -92,6 +95,7 @@ import java.io.IOException;
import java.io.InputStream;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
@ -101,6 +105,7 @@ import java.util.concurrent.ExecutionException;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicBoolean;
import javax.annotation.Nullable;
import javax.net.ssl.SSLException;
import javax.net.ssl.SSLHandshakeException;
@ -538,6 +543,46 @@ public class NettyClientTransportTest {
}
}
@Test
public void huffmanCodingShouldNotBePerformed() throws Exception {
String longStringOfA = Strings.repeat("a", 128);
negotiator = ProtocolNegotiators.serverPlaintext();
startServer();
NettyClientTransport transport = newTransport(ProtocolNegotiators.plaintext(),
DEFAULT_MAX_MESSAGE_SIZE, GrpcUtil.DEFAULT_MAX_HEADER_LIST_SIZE, null, false,
TimeUnit.SECONDS.toNanos(10L), TimeUnit.SECONDS.toNanos(1L),
new ReflectiveChannelFactory<>(NioSocketChannel.class), group);
Metadata headers = new Metadata();
headers.put(Metadata.Key.of("test", Metadata.ASCII_STRING_MARSHALLER),
longStringOfA);
callMeMaybe(transport.start(clientTransportListener));
AtomicBoolean foundExpectedHeaderBytes = new AtomicBoolean(false);
transport.channel().pipeline().addFirst(new ChannelDuplexHandler() {
@Override
public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise)
throws Exception {
if (msg instanceof ByteBuf) {
if (((ByteBuf) msg).toString(StandardCharsets.UTF_8).contains(longStringOfA)) {
foundExpectedHeaderBytes.set(true);
}
}
super.write(ctx, msg, promise);
}
});
new Rpc(transport, headers).halfClose().waitForResponse();
if (!foundExpectedHeaderBytes.get()) {
fail("expected to find UTF-8 encoded 'a's in the header");
}
}
@Test
public void maxHeaderListSizeShouldBeEnforcedOnServer() throws Exception {
startServer(100, 1);