peridot/vendor/go.temporal.io/sdk/internal/grpc_dialer.go

184 lines
7.4 KiB
Go
Raw Normal View History

2022-07-07 20:11:50 +00:00
// The MIT License
//
// Copyright (c) 2020 Temporal Technologies Inc. All rights reserved.
//
// Copyright (c) 2020 Uber Technologies, Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
package internal
import (
"context"
"time"
"github.com/gogo/status"
grpc_retry "github.com/grpc-ecosystem/go-grpc-middleware/retry"
"go.temporal.io/api/serviceerror"
"go.temporal.io/sdk/internal/common/metrics"
"go.temporal.io/sdk/internal/common/retry"
"google.golang.org/grpc"
"google.golang.org/grpc/backoff"
"google.golang.org/grpc/credentials"
"google.golang.org/grpc/keepalive"
"google.golang.org/grpc/metadata"
)
type (
// dialParameters are passed to GRPCDialer and must be used to create gRPC connection.
dialParameters struct {
HostPort string
UserConnectionOptions ConnectionOptions
RequiredInterceptors []grpc.UnaryClientInterceptor
DefaultServiceConfig string
}
)
const (
// LocalHostPort is a default host:port for worker and client to connect to.
LocalHostPort = "localhost:7233"
// defaultServiceConfig is a default gRPC connection service config which enables DNS round-robin between IPs.
defaultServiceConfig = `{"loadBalancingConfig": [{"round_robin":{}}]}`
// minConnectTimeout is the minimum amount of time we are willing to give a connection to complete.
minConnectTimeout = 20 * time.Second
// attemptSuffix is a suffix added to the metric name for individual call attempts made to the server, which includes retries.
attemptSuffix = "_attempt"
// mb is a number of bytes in a megabyte
mb = 1024 * 1024
// defaultMaxPayloadSize is a maximum size of the payload that grpc client would allow.
defaultMaxPayloadSize = 64 * mb
)
func dial(params dialParameters) (*grpc.ClientConn, error) {
var securityOptions []grpc.DialOption
if params.UserConnectionOptions.TLS != nil {
securityOptions = []grpc.DialOption{
grpc.WithTransportCredentials(credentials.NewTLS(params.UserConnectionOptions.TLS)),
}
} else {
securityOptions = []grpc.DialOption{
grpc.WithInsecure(),
grpc.WithAuthority(params.UserConnectionOptions.Authority),
}
}
maxPayloadSize := defaultMaxPayloadSize
if params.UserConnectionOptions.MaxPayloadSize != 0 {
maxPayloadSize = params.UserConnectionOptions.MaxPayloadSize
}
// gRPC maintains connection pool inside grpc.ClientConn.
// This connection pool has auto reconnect feature.
// If connection goes down, gRPC will try to reconnect using exponential backoff strategy:
// https://github.com/grpc/grpc/blob/master/doc/connection-backoff.md.
// Default MaxDelay is 120 seconds which is too high.
// Setting it to retryPollOperationMaxInterval here will correlate with poll reconnect interval.
var cp = grpc.ConnectParams{
Backoff: backoff.DefaultConfig,
MinConnectTimeout: minConnectTimeout,
}
cp.Backoff.BaseDelay = retryPollOperationInitialInterval
cp.Backoff.MaxDelay = retryPollOperationMaxInterval
opts := []grpc.DialOption{
grpc.WithChainUnaryInterceptor(params.RequiredInterceptors...),
grpc.WithDefaultServiceConfig(params.DefaultServiceConfig),
grpc.WithConnectParams(cp),
}
opts = append(opts, securityOptions...)
opts = append(opts, grpc.WithDefaultCallOptions(grpc.MaxCallSendMsgSize(maxPayloadSize)))
opts = append(opts, grpc.WithDefaultCallOptions(grpc.MaxCallRecvMsgSize(maxPayloadSize)))
if params.UserConnectionOptions.EnableKeepAliveCheck {
// gRPC utilizes keep alive mechanism to detect dead connections in case if server didn't close them
// gracefully. Client would ping the server periodically and expect replies withing the specified timeout.
// Learn more by reading https://github.com/grpc/grpc/blob/master/doc/keepalive.md
var kap = keepalive.ClientParameters{
Time: params.UserConnectionOptions.KeepAliveTime,
Timeout: params.UserConnectionOptions.KeepAliveTimeout,
PermitWithoutStream: params.UserConnectionOptions.KeepAlivePermitWithoutStream,
}
opts = append(opts, grpc.WithKeepaliveParams(kap))
}
// Append any user-supplied options
opts = append(opts, params.UserConnectionOptions.DialOptions...)
return grpc.Dial(params.HostPort, opts...)
}
func requiredInterceptors(
metricsHandler metrics.Handler,
headersProvider HeadersProvider,
controller TrafficController,
) []grpc.UnaryClientInterceptor {
interceptors := []grpc.UnaryClientInterceptor{
errorInterceptor,
// Report aggregated metrics for the call, this is done outside of the retry loop.
metrics.NewGRPCInterceptor(metricsHandler, ""),
// By default the grpc retry interceptor *is disabled*, preventing accidental use of retries.
// We add call options for retry configuration based on the values present in the context.
retry.NewRetryOptionsInterceptor(),
// Performs retries *IF* retry options are set for the call.
grpc_retry.UnaryClientInterceptor(),
// Report metrics for every call made to the server.
metrics.NewGRPCInterceptor(metricsHandler, attemptSuffix),
}
if headersProvider != nil {
interceptors = append(interceptors, headersProviderInterceptor(headersProvider))
}
if controller != nil {
interceptors = append(interceptors, trafficControllerInterceptor(controller))
}
return interceptors
}
func trafficControllerInterceptor(controller TrafficController) grpc.UnaryClientInterceptor {
return func(ctx context.Context, method string, req, reply interface{}, cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error {
err := controller.CheckCallAllowed(ctx, method, req, reply)
// Break execution chain and return an error without sending actual request to the server.
if err != nil {
return err
}
return invoker(ctx, method, req, reply, cc, opts...)
}
}
func headersProviderInterceptor(headersProvider HeadersProvider) grpc.UnaryClientInterceptor {
return func(ctx context.Context, method string, req, reply interface{}, cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error {
headers, err := headersProvider.GetHeaders(ctx)
if err != nil {
return err
}
for k, v := range headers {
ctx = metadata.AppendToOutgoingContext(ctx, k, v)
}
return invoker(ctx, method, req, reply, cc, opts...)
}
}
func errorInterceptor(ctx context.Context, method string, req, reply interface{}, cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error {
err := invoker(ctx, method, req, reply, cc, opts...)
err = serviceerror.FromStatus(status.Convert(err))
return err
}