···3737 "github.com/prometheus/client_golang/prometheus/promauto"
3838 sglog "github.com/sourcegraph/log"
3939 "github.com/sourcegraph/zoekt/grpc/internalerrs"
4040+ "github.com/sourcegraph/zoekt/grpc/messagesize"
4041 "go.uber.org/automaxprocs/maxprocs"
4142 "golang.org/x/net/trace"
4243 "golang.org/x/sys/unix"
···13721373 }
1373137413741375 logger := sglog.Scoped("zoektConfigurationGRPCClient", "")
13751375-13761376- gRPCConnectionOptions := []grpc.DialOption{
13771377- grpc.WithTransportCredentials(insecure.NewCredentials()),
13781378- grpc.WithChainStreamInterceptor(
13791379- internalActorStreamInterceptor(),
13801380- internalerrs.LoggingStreamClientInterceptor(logger),
13811381- internalerrs.PrometheusStreamClientInterceptor,
13821382- ),
13831383- grpc.WithChainUnaryInterceptor(
13841384- internalActorUnaryInterceptor(),
13851385- internalerrs.LoggingUnaryClientInterceptor(logger),
13861386- internalerrs.PrometheusUnaryClientInterceptor,
13871387- ),
13881388- grpc.WithDefaultServiceConfig(defaultGRPCServiceConfigurationJSON),
13891389- }
13901390-13911391- // This dialer is used to connect via gRPC to the Sourcegraph instance.
13921392- // This is done lazily, so we can provide the client to use regardless of
13931393- // whether we enabled gRPC or not initially.
13941394- cc, err := grpc.Dial(rootURL.Host, gRPCConnectionOptions...)
13761376+ client, err := dialGRPCClient(rootURL.Host, logger)
13951377 if err != nil {
13961378 return nil, fmt.Errorf("initializing gRPC connection to %q: %w", rootURL.Host, err)
13971379 }
1398138013991399- client := proto.NewZoektConfigurationServiceClient(cc)
14001381 opts = append(opts, WithGRPCClient(client))
14011401-14021382 sg = newSourcegraphClient(rootURL, conf.hostname, opts...)
1403138314041384 } else {
···14701450 ctx = metadata.AppendToOutgoingContext(ctx, "X-Sourcegraph-Actor-UID", "internal")
14711451 return streamer(ctx, desc, cc, method, opts...)
14721452 }
14531453+}
14541454+14551455+// defaultGRPCMessageReceiveSizeBytes is the default message size that gRPCs servers and clients are allowed to process.
14561456+// This can be overridden by providing custom Server/Dial options.
14571457+const defaultGRPCMessageReceiveSizeBytes = 90 * 1024 * 1024 // 90 MB
14581458+14591459+func dialGRPCClient(addr string, logger sglog.Logger, additionalOpts ...grpc.DialOption) (proto.ZoektConfigurationServiceClient, error) {
14601460+ opts := []grpc.DialOption{
14611461+ grpc.WithTransportCredentials(insecure.NewCredentials()),
14621462+ grpc.WithChainStreamInterceptor(
14631463+ internalActorStreamInterceptor(),
14641464+ internalerrs.LoggingStreamClientInterceptor(logger),
14651465+ internalerrs.PrometheusStreamClientInterceptor,
14661466+ ),
14671467+ grpc.WithChainUnaryInterceptor(
14681468+ internalActorUnaryInterceptor(),
14691469+ internalerrs.LoggingUnaryClientInterceptor(logger),
14701470+ internalerrs.PrometheusUnaryClientInterceptor,
14711471+ ),
14721472+ grpc.WithDefaultServiceConfig(defaultGRPCServiceConfigurationJSON),
14731473+ grpc.WithDefaultCallOptions(grpc.MaxCallRecvMsgSize(defaultGRPCMessageReceiveSizeBytes)),
14741474+ }
14751475+14761476+ opts = append(opts, additionalOpts...)
14771477+14781478+ // Ensure that the message size options are set last, so they override any other
14791479+ // client-specific options that tweak the message size.
14801480+ //
14811481+ // The message size options are only provided if the environment variable is set. These options serve as an escape hatch, so they
14821482+ // take precedence over everything else with a uniform size setting that's easy to reason about.
14831483+ opts = append(opts, messagesize.MustGetClientMessageSizeFromEnv()...)
14841484+14851485+ // This dialer is used to connect via gRPC to the Sourcegraph instance.
14861486+ // This is done lazily, so we can provide the client to use regardless of
14871487+ // whether we enabled gRPC or not initially.
14881488+ cc, err := grpc.Dial(addr, opts...)
14891489+ if err != nil {
14901490+ return nil, fmt.Errorf("dialing %q: %w", addr, err)
14911491+ }
14921492+14931493+ client := proto.NewZoektConfigurationServiceClient(cc)
14941494+ return client, nil
14731495}
1474149614751497// addDefaultPort adds a default port to a URL if one is not specified.
+30-11
cmd/zoekt-webserver/main.go
···39394040 "github.com/sourcegraph/mountinfo"
4141 "github.com/sourcegraph/zoekt/grpc/internalerrs"
4242+ "github.com/sourcegraph/zoekt/grpc/messagesize"
4243 zoektgrpc "github.com/sourcegraph/zoekt/grpc/server"
4344 "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc"
4445 "golang.org/x/net/http2"
···291292292293 logger := sglog.Scoped("ZoektWebserverGRPCServer", "The Zoekt Webserver GRPC Server")
293294294294- grpcServer := grpc.NewServer(
295295- grpc.ChainStreamInterceptor(
296296- otelgrpc.StreamServerInterceptor(),
297297- internalerrs.LoggingStreamServerInterceptor(logger),
298298- ),
299299- grpc.ChainUnaryInterceptor(
300300- otelgrpc.UnaryServerInterceptor(),
301301- internalerrs.LoggingUnaryServerInterceptor(logger),
302302- ),
303303- )
304304- v1.RegisterWebserverServiceServer(grpcServer, zoektgrpc.NewServer(web.NewTraceAwareSearcher(s.Searcher)))
295295+ streamer := web.NewTraceAwareSearcher(s.Searcher)
296296+ grpcServer := newGRPCServer(logger, streamer)
305297306298 handler = multiplexGRPC(grpcServer, handler)
307299···634626 }
635627636628 return sglog.TraceContext{}
629629+}
630630+631631+func newGRPCServer(logger sglog.Logger, streamer zoekt.Streamer, additionalOpts ...grpc.ServerOption) *grpc.Server {
632632+ opts := []grpc.ServerOption{
633633+ grpc.ChainStreamInterceptor(
634634+ otelgrpc.StreamServerInterceptor(),
635635+ internalerrs.LoggingStreamServerInterceptor(logger),
636636+ ),
637637+ grpc.ChainUnaryInterceptor(
638638+ otelgrpc.UnaryServerInterceptor(),
639639+ internalerrs.LoggingUnaryServerInterceptor(logger),
640640+ ),
641641+ }
642642+643643+ opts = append(opts, additionalOpts...)
644644+645645+ // Ensure that the message size options are set last, so they override any other
646646+ // server-specific options that tweak the message size.
647647+ //
648648+ // The message size options are only provided if the environment variable is set. These options serve as an escape hatch, so they
649649+ // take precedence over everything else with a uniform size setting that's easy to reason about.
650650+ opts = append(opts, messagesize.MustGetServerMessageSizeFromEnv()...)
651651+652652+ s := grpc.NewServer(opts...)
653653+ v1.RegisterWebserverServiceServer(s, zoektgrpc.NewServer(streamer))
654654+655655+ return s
637656}
638657639658var (
+127
grpc/messagesize/messagesize.go
···11+package messagesize
22+33+import (
44+ "fmt"
55+ "math"
66+ "os"
77+88+ "google.golang.org/grpc"
99+1010+ "github.com/dustin/go-humanize"
1111+)
1212+1313+var (
1414+ smallestAllowedMaxMessageSize = uint64(4 * 1024 * 1024) // 4 MB: There isn't a scenario where we'd want to dip below the default of 4MB.
1515+ largestAllowedMaxMessageSize = uint64(math.MaxInt) // This is the largest allowed value for the type accepted by the grpc.MaxSize[...] options.
1616+1717+ envClientMessageSize = getEnv("GRPC_CLIENT_MAX_MESSAGE_SIZE", messageSizeDisabled) // set the maximum message size for gRPC clients (ex: "40MB")
1818+ envServerMessageSize = getEnv("GRPC_SERVER_MAX_MESSAGE_SIZE", messageSizeDisabled) // set the maximum message size for gRPC servers (ex: "40MB")
1919+2020+ messageSizeDisabled = "message_size_disabled" // sentinel value for when the message size env var isn't set
2121+)
2222+2323+// MustGetClientMessageSizeFromEnv returns a slice of grpc.DialOptions that set the maximum message size for gRPC clients if
2424+// the "SRC_GRPC_CLIENT_MAX_MESSAGE_SIZE" environment variable is set to a valid size value (ex: "40 MB").
2525+//
2626+// If the environment variable isn't set, it returns nil.
2727+// If the size value in the environment variable is invalid (too small, not parsable, etc.), it panics.
2828+func MustGetClientMessageSizeFromEnv() []grpc.DialOption {
2929+ if envClientMessageSize == messageSizeDisabled {
3030+ return nil
3131+ }
3232+3333+ messageSize, err := getMessageSizeBytesFromString(envClientMessageSize, smallestAllowedMaxMessageSize, largestAllowedMaxMessageSize)
3434+ if err != nil {
3535+ panic(fmt.Sprintf("failed to get gRPC client message size: %s", err))
3636+ }
3737+3838+ return []grpc.DialOption{
3939+ grpc.WithDefaultCallOptions(
4040+ grpc.MaxCallRecvMsgSize(messageSize),
4141+ grpc.MaxCallSendMsgSize(messageSize),
4242+ ),
4343+ }
4444+}
4545+4646+// MustGetServerMessageSizeFromEnv returns a slice of grpc.ServerOption that set the maximum message size for gRPC servers if
4747+// the "SRC_GRPC_SERVER_MAX_MESSAGE_SIZE" environment variable is set to a valid size value (ex: "40 MB").
4848+//
4949+// If the environment variable isn't set, it returns nil.
5050+// If the size value in the environment variable is invalid (too small, not parsable, etc.), it panics.
5151+func MustGetServerMessageSizeFromEnv() []grpc.ServerOption {
5252+ if envServerMessageSize == messageSizeDisabled {
5353+ return nil
5454+ }
5555+5656+ messageSize, err := getMessageSizeBytesFromString(envServerMessageSize, smallestAllowedMaxMessageSize, largestAllowedMaxMessageSize)
5757+ if err != nil {
5858+ panic(fmt.Sprintf("failed to get gRPC server message size: %s", err))
5959+ }
6060+6161+ return []grpc.ServerOption{
6262+ grpc.MaxRecvMsgSize(messageSize),
6363+ grpc.MaxSendMsgSize(messageSize),
6464+ }
6565+}
6666+6767+// getMessageSizeBytesFromEnv parses rawSize returns the message size in bytes within the range [minSize, maxSize].
6868+//
6969+// If rawSize isn't a valid size is not set or the value is outside the allowed range, it returns an error.
7070+func getMessageSizeBytesFromString(rawSize string, minSize, maxSize uint64) (size int, err error) {
7171+ sizeBytes, err := humanize.ParseBytes(rawSize)
7272+ if err != nil {
7373+ return 0, &parseError{
7474+ rawSize: rawSize,
7575+ err: err,
7676+ }
7777+ }
7878+7979+ if sizeBytes < minSize || sizeBytes > maxSize {
8080+ return 0, &sizeOutOfRangeError{
8181+ size: humanize.IBytes(sizeBytes),
8282+ min: humanize.IBytes(minSize),
8383+ max: humanize.IBytes(maxSize),
8484+ }
8585+ }
8686+8787+ return int(sizeBytes), nil
8888+}
8989+9090+// parseError occurs when the environment variable's value cannot be parsed as a byte size.
9191+type parseError struct {
9292+ // rawSize is the raw size string that was attempted to be parsed
9393+ rawSize string
9494+ // err is the error that occurred while parsing rawSize
9595+ err error
9696+}
9797+9898+func (e *parseError) Error() string {
9999+ return fmt.Sprintf("failed to parse %q as bytes: %s", e.rawSize, e.err)
100100+}
101101+102102+func (e *parseError) Unwrap() error {
103103+ return e.err
104104+}
105105+106106+// sizeOutOfRangeError occurs when the environment variable's value is outside of the allowed range.
107107+type sizeOutOfRangeError struct {
108108+ // size is the size that was out of range
109109+ size string
110110+ // min is the minimum allowed size
111111+ min string
112112+ // max is the maximum allowed size
113113+ max string
114114+}
115115+116116+func (e *sizeOutOfRangeError) Error() string {
117117+ return fmt.Sprintf("size %s is outside of allowed range [%s, %s]", e.size, e.min, e.max)
118118+}
119119+120120+func getEnv(key string, defaultValue string) string {
121121+ value, ok := os.LookupEnv(key)
122122+ if !ok {
123123+ return defaultValue
124124+ }
125125+126126+ return value
127127+}