github.com/cloudwego/kitex@v0.9.0/client/client.go (about)

     1  /*
     2   * Copyright 2021 CloudWeGo Authors
     3   *
     4   * Licensed under the Apache License, Version 2.0 (the "License");
     5   * you may not use this file except in compliance with the License.
     6   * You may obtain a copy of the License at
     7   *
     8   *     http://www.apache.org/licenses/LICENSE-2.0
     9   *
    10   * Unless required by applicable law or agreed to in writing, software
    11   * distributed under the License is distributed on an "AS IS" BASIS,
    12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13   * See the License for the specific language governing permissions and
    14   * limitations under the License.
    15   */
    16  
    17  package client
    18  
    19  import (
    20  	"context"
    21  	"errors"
    22  	"fmt"
    23  	"runtime"
    24  	"runtime/debug"
    25  	"strconv"
    26  	"sync/atomic"
    27  
    28  	"github.com/bytedance/gopkg/cloud/metainfo"
    29  	"github.com/cloudwego/localsession/backup"
    30  
    31  	"github.com/cloudwego/kitex/client/callopt"
    32  	"github.com/cloudwego/kitex/internal/client"
    33  	"github.com/cloudwego/kitex/pkg/acl"
    34  	"github.com/cloudwego/kitex/pkg/consts"
    35  	"github.com/cloudwego/kitex/pkg/diagnosis"
    36  	"github.com/cloudwego/kitex/pkg/discovery"
    37  	"github.com/cloudwego/kitex/pkg/endpoint"
    38  	"github.com/cloudwego/kitex/pkg/event"
    39  	"github.com/cloudwego/kitex/pkg/fallback"
    40  	"github.com/cloudwego/kitex/pkg/kerrors"
    41  	"github.com/cloudwego/kitex/pkg/klog"
    42  	"github.com/cloudwego/kitex/pkg/loadbalance"
    43  	"github.com/cloudwego/kitex/pkg/loadbalance/lbcache"
    44  	"github.com/cloudwego/kitex/pkg/proxy"
    45  	"github.com/cloudwego/kitex/pkg/remote"
    46  	"github.com/cloudwego/kitex/pkg/remote/bound"
    47  	"github.com/cloudwego/kitex/pkg/remote/remotecli"
    48  	"github.com/cloudwego/kitex/pkg/retry"
    49  	"github.com/cloudwego/kitex/pkg/rpcinfo"
    50  	"github.com/cloudwego/kitex/pkg/rpcinfo/remoteinfo"
    51  	"github.com/cloudwego/kitex/pkg/rpctimeout"
    52  	"github.com/cloudwego/kitex/pkg/serviceinfo"
    53  	"github.com/cloudwego/kitex/pkg/utils"
    54  	"github.com/cloudwego/kitex/pkg/warmup"
    55  	"github.com/cloudwego/kitex/transport"
    56  )
    57  
    58  // Client is the core interface abstraction of kitex client.
    59  // It is designed for generated codes and should not be used directly.
    60  // Parameter method specifies the method of a RPC call.
    61  // Request is a packing of request parameters in the actual method defined in IDL, consist of zero, one
    62  // or multiple arguments. So is response to the actual result type.
    63  // Response may be nil to address oneway calls.
    64  type Client interface {
    65  	Call(ctx context.Context, method string, request, response interface{}) error
    66  }
    67  
    68  type kClient struct {
    69  	svcInfo *serviceinfo.ServiceInfo
    70  	mws     []endpoint.Middleware
    71  	eps     endpoint.Endpoint
    72  	sEps    endpoint.Endpoint
    73  	opt     *client.Options
    74  	lbf     *lbcache.BalancerFactory
    75  
    76  	inited bool
    77  	closed bool
    78  }
    79  
    80  // Set finalizer on kClient does not take effect, because kClient has a circular reference problem
    81  // when construct the endpoint.Endpoint in the invokeHandleEndpoint,
    82  // so wrapping kClient as kcFinalizerClient, and set finalizer on kcFinalizerClient, it can solve this problem.
    83  type kcFinalizerClient struct {
    84  	*kClient
    85  }
    86  
    87  func (kf *kcFinalizerClient) Call(ctx context.Context, method string, request, response interface{}) error {
    88  	defer runtime.KeepAlive(kf)
    89  	return kf.kClient.Call(ctx, method, request, response)
    90  }
    91  
    92  // NewClient creates a kitex.Client with the given ServiceInfo, it is from generated code.
    93  func NewClient(svcInfo *serviceinfo.ServiceInfo, opts ...Option) (Client, error) {
    94  	if svcInfo == nil {
    95  		return nil, errors.New("NewClient: no service info")
    96  	}
    97  	kc := &kcFinalizerClient{kClient: &kClient{}}
    98  	kc.svcInfo = svcInfo
    99  	kc.opt = client.NewOptions(opts)
   100  	if err := kc.init(); err != nil {
   101  		_ = kc.Close()
   102  		return nil, err
   103  	}
   104  	// like os.File, if kc is garbage-collected, but Close is not called, call Close.
   105  	runtime.SetFinalizer(kc, func(c *kcFinalizerClient) {
   106  		_ = c.Close()
   107  	})
   108  	return kc, nil
   109  }
   110  
   111  func (kc *kClient) init() (err error) {
   112  	initTransportProtocol(kc.svcInfo, kc.opt.Configs)
   113  	if err = kc.checkOptions(); err != nil {
   114  		return err
   115  	}
   116  	if err = kc.initCircuitBreaker(); err != nil {
   117  		return err
   118  	}
   119  	if err = kc.initRetryer(); err != nil {
   120  		return err
   121  	}
   122  	if err = kc.initProxy(); err != nil {
   123  		return err
   124  	}
   125  	if err = kc.initConnPool(); err != nil {
   126  		return err
   127  	}
   128  	if err = kc.initLBCache(); err != nil {
   129  		return err
   130  	}
   131  	ctx := kc.initContext()
   132  	kc.initMiddlewares(ctx)
   133  	kc.initStreamMiddlewares(ctx)
   134  	kc.initDebugService()
   135  	kc.richRemoteOption()
   136  	if err = kc.buildInvokeChain(); err != nil {
   137  		return err
   138  	}
   139  	if err = kc.warmingUp(); err != nil {
   140  		return err
   141  	}
   142  	kc.inited = true
   143  	return nil
   144  }
   145  
   146  func (kc *kClient) checkOptions() (err error) {
   147  	if kc.opt.Svr.ServiceName == "" {
   148  		return errors.New("service name is required")
   149  	}
   150  	return nil
   151  }
   152  
   153  func (kc *kClient) initCircuitBreaker() error {
   154  	if kc.opt.CBSuite != nil {
   155  		kc.opt.CBSuite.SetEventBusAndQueue(kc.opt.Bus, kc.opt.Events)
   156  	}
   157  	return nil
   158  }
   159  
   160  func (kc *kClient) initRetryer() error {
   161  	if kc.opt.RetryContainer == nil {
   162  		if kc.opt.RetryMethodPolicies == nil {
   163  			return nil
   164  		}
   165  		kc.opt.InitRetryContainer()
   166  	}
   167  	return kc.opt.RetryContainer.Init(kc.opt.RetryMethodPolicies, kc.opt.RetryWithResult)
   168  }
   169  
   170  func (kc *kClient) initContext() context.Context {
   171  	ctx := context.Background()
   172  	ctx = context.WithValue(ctx, endpoint.CtxEventBusKey, kc.opt.Bus)
   173  	ctx = context.WithValue(ctx, endpoint.CtxEventQueueKey, kc.opt.Events)
   174  	ctx = context.WithValue(ctx, rpctimeout.TimeoutAdjustKey, &kc.opt.ExtraTimeout)
   175  	if chr, ok := kc.opt.Proxy.(proxy.ContextHandler); ok {
   176  		ctx = chr.HandleContext(ctx)
   177  	}
   178  	return ctx
   179  }
   180  
   181  func (kc *kClient) initProxy() error {
   182  	if kc.opt.Proxy != nil {
   183  		cfg := proxy.Config{
   184  			ServerInfo:   kc.opt.Svr,
   185  			Resolver:     kc.opt.Resolver,
   186  			Balancer:     kc.opt.Balancer,
   187  			Pool:         kc.opt.RemoteOpt.ConnPool,
   188  			FixedTargets: kc.opt.Targets,
   189  			RPCConfig:    kc.opt.Configs,
   190  		}
   191  		if err := kc.opt.Proxy.Configure(&cfg); err != nil {
   192  			return err
   193  		}
   194  		// update fields in the client option for further use.
   195  		kc.opt.Resolver = cfg.Resolver
   196  		kc.opt.Balancer = cfg.Balancer
   197  		// close predefined pool when proxy init new pool.
   198  		if cfg.Pool != kc.opt.RemoteOpt.ConnPool && kc.opt.RemoteOpt.ConnPool != nil {
   199  			kc.opt.RemoteOpt.ConnPool.Close()
   200  		}
   201  		kc.opt.RemoteOpt.ConnPool = cfg.Pool
   202  		kc.opt.Targets = cfg.FixedTargets
   203  	}
   204  	return nil
   205  }
   206  
   207  func (kc *kClient) initConnPool() error {
   208  	pool := kc.opt.RemoteOpt.ConnPool
   209  	kc.opt.CloseCallbacks = append(kc.opt.CloseCallbacks, pool.Close)
   210  
   211  	if df, ok := pool.(interface{ Dump() interface{} }); ok {
   212  		kc.opt.DebugService.RegisterProbeFunc(diagnosis.ConnPoolKey, df.Dump)
   213  	}
   214  	if r, ok := pool.(remote.ConnPoolReporter); ok && kc.opt.RemoteOpt.EnableConnPoolReporter {
   215  		r.EnableReporter()
   216  	}
   217  
   218  	if long, ok := pool.(remote.LongConnPool); ok {
   219  		kc.opt.Bus.Watch(discovery.ChangeEventName, func(ev *event.Event) {
   220  			ch, ok := ev.Extra.(*discovery.Change)
   221  			if !ok {
   222  				return
   223  			}
   224  			for _, inst := range ch.Removed {
   225  				if addr := inst.Address(); addr != nil {
   226  					long.Clean(addr.Network(), addr.String())
   227  				}
   228  			}
   229  		})
   230  	}
   231  	return nil
   232  }
   233  
   234  func (kc *kClient) initLBCache() error {
   235  	if kc.opt.Proxy != nil && kc.opt.Resolver == nil {
   236  		return nil
   237  	}
   238  	onChange := discoveryEventHandler(discovery.ChangeEventName, kc.opt.Bus, kc.opt.Events)
   239  	onDelete := discoveryEventHandler(discovery.DeleteEventName, kc.opt.Bus, kc.opt.Events)
   240  	resolver := kc.opt.Resolver
   241  	if resolver == nil {
   242  		// fake a resolver instead of returning an error directly because users may use
   243  		// callopt.WithHostPort to specify target addresses after NewClient.
   244  		resolver = &discovery.SynthesizedResolver{
   245  			ResolveFunc: func(ctx context.Context, target string) (discovery.Result, error) {
   246  				return discovery.Result{}, kerrors.ErrNoResolver
   247  			},
   248  			NameFunc: func() string { return "no_resolver" },
   249  		}
   250  	}
   251  	// because we cannot ensure that user's custom loadbalancer is cacheable, we need to disable it here
   252  	cacheOpts := lbcache.Options{DiagnosisService: kc.opt.DebugService, Cacheable: false}
   253  	balancer := kc.opt.Balancer
   254  	if balancer == nil {
   255  		// default internal lb balancer is cacheable
   256  		cacheOpts.Cacheable = true
   257  		balancer = loadbalance.NewWeightedBalancer()
   258  	}
   259  	if kc.opt.BalancerCacheOpt != nil {
   260  		cacheOpts = *kc.opt.BalancerCacheOpt
   261  	}
   262  	kc.lbf = lbcache.NewBalancerFactory(resolver, balancer, cacheOpts)
   263  	rbIdx := kc.lbf.RegisterRebalanceHook(onChange)
   264  	kc.opt.CloseCallbacks = append(kc.opt.CloseCallbacks, func() error {
   265  		kc.lbf.DeregisterRebalanceHook(rbIdx)
   266  		return nil
   267  	})
   268  	dIdx := kc.lbf.RegisterDeleteHook(onDelete)
   269  	kc.opt.CloseCallbacks = append(kc.opt.CloseCallbacks, func() error {
   270  		kc.lbf.DeregisterDeleteHook(dIdx)
   271  		return nil
   272  	})
   273  	return nil
   274  }
   275  
   276  func (kc *kClient) initMiddlewares(ctx context.Context) {
   277  	builderMWs := richMWsWithBuilder(ctx, kc.opt.MWBs)
   278  	// integrate xds if enabled
   279  	if kc.opt.XDSEnabled && kc.opt.XDSRouterMiddleware != nil && kc.opt.Proxy == nil {
   280  		kc.mws = append(kc.mws, kc.opt.XDSRouterMiddleware)
   281  	}
   282  	kc.mws = append(kc.mws, kc.opt.CBSuite.ServiceCBMW(), rpcTimeoutMW(ctx), contextMW)
   283  	kc.mws = append(kc.mws, builderMWs...)
   284  	kc.mws = append(kc.mws, acl.NewACLMiddleware(kc.opt.ACLRules))
   285  	if kc.opt.Proxy == nil {
   286  		kc.mws = append(kc.mws, newResolveMWBuilder(kc.lbf)(ctx))
   287  		kc.mws = append(kc.mws, kc.opt.CBSuite.InstanceCBMW())
   288  		kc.mws = append(kc.mws, richMWsWithBuilder(ctx, kc.opt.IMWBs)...)
   289  	} else {
   290  		if kc.opt.Resolver != nil { // customized service discovery
   291  			kc.mws = append(kc.mws, newResolveMWBuilder(kc.lbf)(ctx))
   292  		}
   293  		kc.mws = append(kc.mws, newProxyMW(kc.opt.Proxy))
   294  	}
   295  	kc.mws = append(kc.mws, newIOErrorHandleMW(kc.opt.ErrHandle))
   296  }
   297  
   298  func (kc *kClient) initStreamMiddlewares(ctx context.Context) {
   299  	kc.opt.Streaming.EventHandler = kc.opt.TracerCtl.GetStreamEventHandler()
   300  	kc.opt.Streaming.InitMiddlewares(ctx)
   301  }
   302  
   303  func richMWsWithBuilder(ctx context.Context, mwBs []endpoint.MiddlewareBuilder) (mws []endpoint.Middleware) {
   304  	for i := range mwBs {
   305  		mws = append(mws, mwBs[i](ctx))
   306  	}
   307  	return
   308  }
   309  
   310  // initRPCInfo initializes the RPCInfo structure and attaches it to context.
   311  func (kc *kClient) initRPCInfo(ctx context.Context, method string, retryTimes int, firstRI rpcinfo.RPCInfo) (context.Context, rpcinfo.RPCInfo, *callopt.CallOptions) {
   312  	return initRPCInfo(ctx, method, kc.opt, kc.svcInfo, retryTimes, firstRI)
   313  }
   314  
   315  func applyCallOptions(ctx context.Context, cfg rpcinfo.MutableRPCConfig, svr remoteinfo.RemoteInfo, opt *client.Options) (context.Context, *callopt.CallOptions) {
   316  	cos := CallOptionsFromCtx(ctx)
   317  	if len(cos) > 0 {
   318  		info, callOpts := callopt.Apply(cos, cfg, svr, opt.Locks, opt.HTTPResolver)
   319  		ctx = context.WithValue(ctx, ctxCallOptionInfoKey, info)
   320  		return ctx, callOpts
   321  	}
   322  	opt.Locks.ApplyLocks(cfg, svr)
   323  	return ctx, nil
   324  }
   325  
   326  // Call implements the Client interface .
   327  func (kc *kClient) Call(ctx context.Context, method string, request, response interface{}) (err error) {
   328  	// merge backup context if no metainfo found in ctx
   329  	ctx = backup.RecoverCtxOnDemands(ctx, kc.opt.CtxBackupHandler)
   330  
   331  	validateForCall(ctx, kc.inited, kc.closed)
   332  	var ri rpcinfo.RPCInfo
   333  	var callOpts *callopt.CallOptions
   334  	ctx, ri, callOpts = kc.initRPCInfo(ctx, method, 0, nil)
   335  
   336  	ctx = kc.opt.TracerCtl.DoStart(ctx, ri)
   337  	var reportErr error
   338  	var recycleRI bool
   339  	defer func() {
   340  		if panicInfo := recover(); panicInfo != nil {
   341  			err = rpcinfo.ClientPanicToErr(ctx, panicInfo, ri, false)
   342  			reportErr = err
   343  		}
   344  		kc.opt.TracerCtl.DoFinish(ctx, ri, reportErr)
   345  		if recycleRI {
   346  			// why need check recycleRI to decide if recycle RPCInfo?
   347  			// 1. no retry, rpc timeout happen will cause panic when response return
   348  			// 2. retry success, will cause panic when first call return
   349  			// 3. backup request may cause panic, cannot recycle first RPCInfo
   350  			// RPCInfo will be recycled after rpc is finished,
   351  			// holding RPCInfo in a new goroutine is forbidden.
   352  			rpcinfo.PutRPCInfo(ri)
   353  		}
   354  		callOpts.Recycle()
   355  	}()
   356  
   357  	callOptRetry := getCalloptRetryPolicy(callOpts)
   358  	if kc.opt.RetryContainer == nil && callOptRetry != nil && callOptRetry.Enable {
   359  		// setup retry in callopt
   360  		kc.opt.InitRetryContainer()
   361  	}
   362  
   363  	// Add necessary keys to context for isolation between kitex client method calls
   364  	ctx = retry.PrepareRetryContext(ctx)
   365  
   366  	if kc.opt.RetryContainer == nil {
   367  		// call without retry policy
   368  		err = kc.eps(ctx, request, response)
   369  		if err == nil {
   370  			recycleRI = true
   371  		}
   372  	} else {
   373  		var lastRI rpcinfo.RPCInfo
   374  		lastRI, recycleRI, err = kc.opt.RetryContainer.WithRetryIfNeeded(ctx, callOptRetry, kc.rpcCallWithRetry(ri, method, request, response), ri, request)
   375  		if ri != lastRI {
   376  			// reset ri of ctx to lastRI
   377  			ctx = rpcinfo.NewCtxWithRPCInfo(ctx, lastRI)
   378  		}
   379  		ri = lastRI
   380  	}
   381  
   382  	// do fallback if with setup
   383  	err, reportErr = doFallbackIfNeeded(ctx, ri, request, response, err, kc.opt.Fallback, callOpts)
   384  	return err
   385  }
   386  
   387  func (kc *kClient) rpcCallWithRetry(ri rpcinfo.RPCInfo, method string, request, response interface{}) retry.RPCCallFunc {
   388  	// call with retry policy
   389  	var callTimes int32
   390  	// prevRI represents a value of rpcinfo.RPCInfo type.
   391  	var prevRI atomic.Value
   392  	return func(ctx context.Context, r retry.Retryer) (rpcinfo.RPCInfo, interface{}, error) {
   393  		currCallTimes := int(atomic.AddInt32(&callTimes, 1))
   394  		cRI := ri
   395  		if currCallTimes > 1 {
   396  			ctx, cRI, _ = kc.initRPCInfo(ctx, method, currCallTimes-1, ri)
   397  			ctx = metainfo.WithPersistentValue(ctx, retry.TransitKey, strconv.Itoa(currCallTimes-1))
   398  			if prevRI.Load() == nil {
   399  				prevRI.Store(ri)
   400  			}
   401  			r.Prepare(ctx, prevRI.Load().(rpcinfo.RPCInfo), cRI)
   402  			prevRI.Store(cRI)
   403  		}
   404  		callErr := kc.eps(ctx, request, response)
   405  		return cRI, response, callErr
   406  	}
   407  }
   408  
   409  func (kc *kClient) initDebugService() {
   410  	if ds := kc.opt.DebugService; ds != nil {
   411  		ds.RegisterProbeFunc(diagnosis.DestServiceKey, diagnosis.WrapAsProbeFunc(kc.opt.Svr.ServiceName))
   412  		ds.RegisterProbeFunc(diagnosis.OptionsKey, diagnosis.WrapAsProbeFunc(kc.opt.DebugInfo))
   413  		ds.RegisterProbeFunc(diagnosis.ChangeEventsKey, kc.opt.Events.Dump)
   414  		ds.RegisterProbeFunc(diagnosis.ServiceInfosKey, diagnosis.WrapAsProbeFunc(map[string]*serviceinfo.ServiceInfo{kc.svcInfo.ServiceName: kc.svcInfo}))
   415  	}
   416  }
   417  
   418  func (kc *kClient) richRemoteOption() {
   419  	kc.opt.RemoteOpt.SvcInfo = kc.svcInfo
   420  	// for client trans info handler
   421  	if len(kc.opt.MetaHandlers) > 0 {
   422  		// TODO in stream situations, meta is only assembled when the stream creates
   423  		// metaHandler needs to be called separately.
   424  		// (newClientStreamer: call WriteMeta before remotecli.NewClient)
   425  		transInfoHdlr := bound.NewTransMetaHandler(kc.opt.MetaHandlers)
   426  		kc.opt.RemoteOpt.PrependBoundHandler(transInfoHdlr)
   427  	}
   428  }
   429  
   430  func (kc *kClient) buildInvokeChain() error {
   431  	innerHandlerEp, err := kc.invokeHandleEndpoint()
   432  	if err != nil {
   433  		return err
   434  	}
   435  	kc.eps = endpoint.Chain(kc.mws...)(innerHandlerEp)
   436  
   437  	innerStreamingEp, err := kc.invokeStreamingEndpoint()
   438  	if err != nil {
   439  		return err
   440  	}
   441  	kc.sEps = endpoint.Chain(kc.mws...)(innerStreamingEp)
   442  	return nil
   443  }
   444  
   445  func (kc *kClient) invokeHandleEndpoint() (endpoint.Endpoint, error) {
   446  	transPipl, err := newCliTransHandler(kc.opt.RemoteOpt)
   447  	if err != nil {
   448  		return nil, err
   449  	}
   450  	return func(ctx context.Context, req, resp interface{}) (err error) {
   451  		var sendMsg remote.Message
   452  		var recvMsg remote.Message
   453  		defer func() {
   454  			remote.RecycleMessage(sendMsg)
   455  			// Notice, recycle and decode may race if decode exec in another goroutine.
   456  			// No race now, it is ok to recycle. Or recvMsg recycle depend on recv err
   457  			remote.RecycleMessage(recvMsg)
   458  		}()
   459  		ri := rpcinfo.GetRPCInfo(ctx)
   460  		methodName := ri.Invocation().MethodName()
   461  
   462  		cli, err := remotecli.NewClient(ctx, ri, transPipl, kc.opt.RemoteOpt)
   463  		if err != nil {
   464  			return
   465  		}
   466  
   467  		defer cli.Recycle()
   468  		config := ri.Config()
   469  		m := kc.svcInfo.MethodInfo(methodName)
   470  		if m == nil {
   471  			return fmt.Errorf("method info is nil, methodName=%s, serviceInfo=%+v", methodName, kc.svcInfo)
   472  		} else if m.OneWay() {
   473  			sendMsg = remote.NewMessage(req, kc.svcInfo, ri, remote.Oneway, remote.Client)
   474  		} else {
   475  			sendMsg = remote.NewMessage(req, kc.svcInfo, ri, remote.Call, remote.Client)
   476  		}
   477  		protocolInfo := remote.NewProtocolInfo(config.TransportProtocol(), kc.svcInfo.PayloadCodec)
   478  		sendMsg.SetProtocolInfo(protocolInfo)
   479  
   480  		if err = cli.Send(ctx, ri, sendMsg); err != nil {
   481  			return
   482  		}
   483  		if m.OneWay() {
   484  			cli.Recv(ctx, ri, nil)
   485  			return nil
   486  		}
   487  
   488  		recvMsg = remote.NewMessage(resp, kc.opt.RemoteOpt.SvcInfo, ri, remote.Reply, remote.Client)
   489  		recvMsg.SetProtocolInfo(protocolInfo)
   490  		err = cli.Recv(ctx, ri, recvMsg)
   491  		return err
   492  	}, nil
   493  }
   494  
   495  // Close is not concurrency safe.
   496  func (kc *kClient) Close() error {
   497  	defer func() {
   498  		if err := recover(); err != nil {
   499  			klog.Warnf("KITEX: panic when close client, error=%s, stack=%s", err, string(debug.Stack()))
   500  		}
   501  	}()
   502  	if kc.closed {
   503  		return nil
   504  	}
   505  	kc.closed = true
   506  	var errs utils.ErrChain
   507  	for _, cb := range kc.opt.CloseCallbacks {
   508  		if err := cb(); err != nil {
   509  			errs.Append(err)
   510  		}
   511  	}
   512  	if kc.opt.CBSuite != nil {
   513  		if err := kc.opt.CBSuite.Close(); err != nil {
   514  			errs.Append(err)
   515  		}
   516  	}
   517  	if errs.HasError() {
   518  		return errs
   519  	}
   520  	return nil
   521  }
   522  
   523  func newCliTransHandler(opt *remote.ClientOption) (remote.ClientTransHandler, error) {
   524  	handler, err := opt.CliHandlerFactory.NewTransHandler(opt)
   525  	if err != nil {
   526  		return nil, err
   527  	}
   528  	transPl := remote.NewTransPipeline(handler)
   529  	for _, ib := range opt.Inbounds {
   530  		transPl.AddInboundHandler(ib)
   531  	}
   532  	for _, ob := range opt.Outbounds {
   533  		transPl.AddOutboundHandler(ob)
   534  	}
   535  	return transPl, nil
   536  }
   537  
   538  func initTransportProtocol(svcInfo *serviceinfo.ServiceInfo, cfg rpcinfo.RPCConfig) {
   539  	mutableRPCConfig := rpcinfo.AsMutableRPCConfig(cfg)
   540  	mutableRPCConfig.SetPayloadCodec(svcInfo.PayloadCodec)
   541  	if svcInfo.PayloadCodec == serviceinfo.Protobuf && cfg.TransportProtocol()&transport.GRPC != transport.GRPC {
   542  		// pb use ttheader framed by default
   543  		mutableRPCConfig.SetTransportProtocol(transport.TTHeaderFramed)
   544  	}
   545  }
   546  
   547  func (kc *kClient) warmingUp() error {
   548  	if kc.opt.WarmUpOption == nil {
   549  		return nil
   550  	}
   551  	wuo := kc.opt.WarmUpOption
   552  	doWarmupPool := wuo.PoolOption != nil && kc.opt.Proxy == nil
   553  
   554  	// service discovery
   555  	if kc.opt.Resolver == nil {
   556  		return nil
   557  	}
   558  	nas := make(map[string][]string)
   559  	ctx := context.Background()
   560  
   561  	var dests []rpcinfo.EndpointInfo
   562  	if ro := kc.opt.WarmUpOption.ResolverOption; ro != nil {
   563  		for _, d := range ro.Dests {
   564  			dests = append(dests, rpcinfo.FromBasicInfo(d))
   565  		}
   566  	}
   567  	if len(dests) == 0 && doWarmupPool && len(wuo.PoolOption.Targets) == 0 {
   568  		// build a default destination for the resolver
   569  		cfg := rpcinfo.AsMutableRPCConfig(kc.opt.Configs).Clone()
   570  		rmt := remoteinfo.NewRemoteInfo(kc.opt.Svr, "*")
   571  		ctx, _ = applyCallOptions(ctx, cfg, rmt, kc.opt)
   572  		dests = append(dests, rmt.ImmutableView())
   573  	}
   574  
   575  	for _, dest := range dests {
   576  		lb, err := kc.lbf.Get(ctx, dest)
   577  		if err != nil {
   578  			switch kc.opt.WarmUpOption.ErrorHandling {
   579  			case warmup.IgnoreError:
   580  			case warmup.WarningLog:
   581  				klog.Warnf("KITEX: failed to warm up service discovery: %s", err.Error())
   582  			case warmup.ErrorLog:
   583  				klog.Errorf("KITEX: failed to warm up service discovery: %s", err.Error())
   584  			case warmup.FailFast:
   585  				return fmt.Errorf("service discovery warm-up: %w", err)
   586  			}
   587  			continue
   588  		}
   589  		if res, ok := lb.GetResult(); ok {
   590  			for _, i := range res.Instances {
   591  				if addr := i.Address(); addr != nil {
   592  					n, a := addr.Network(), addr.String()
   593  					nas[n] = append(nas[n], a)
   594  				}
   595  			}
   596  		}
   597  	}
   598  
   599  	// connection pool
   600  	if !doWarmupPool {
   601  		return nil
   602  	}
   603  
   604  	if len(wuo.PoolOption.Targets) == 0 {
   605  		wuo.PoolOption.Targets = nas
   606  	}
   607  
   608  	pool := kc.opt.RemoteOpt.ConnPool
   609  	if wp, ok := pool.(warmup.Pool); ok {
   610  		co := remote.ConnOption{
   611  			Dialer:         kc.opt.RemoteOpt.Dialer,
   612  			ConnectTimeout: kc.opt.Configs.ConnectTimeout(),
   613  		}
   614  		err := wp.WarmUp(kc.opt.WarmUpOption.ErrorHandling, wuo.PoolOption, co)
   615  		if err != nil {
   616  			switch kc.opt.WarmUpOption.ErrorHandling {
   617  			case warmup.IgnoreError:
   618  			case warmup.WarningLog:
   619  				klog.Warnf("KITEX: failed to warm up connection pool: %s", err.Error())
   620  			case warmup.ErrorLog:
   621  				klog.Errorf("KITEX: failed to warm up connection pool: %s", err.Error())
   622  			case warmup.FailFast:
   623  				return fmt.Errorf("connection pool warm-up: %w", err)
   624  			}
   625  		}
   626  	} else {
   627  		klog.Warnf("KITEX: connection pool<%T> does not support warm-up operation", pool)
   628  	}
   629  
   630  	return nil
   631  }
   632  
   633  func validateForCall(ctx context.Context, inited, closed bool) {
   634  	if !inited {
   635  		panic("client not initialized")
   636  	}
   637  	if closed {
   638  		panic("client is already closed")
   639  	}
   640  	if ctx == nil {
   641  		panic("ctx is nil")
   642  	}
   643  }
   644  
   645  func getCalloptRetryPolicy(callOpts *callopt.CallOptions) (callOptRetry *retry.Policy) {
   646  	if callOpts != nil {
   647  		if callOpts.RetryPolicy.Enable {
   648  			callOptRetry = &callOpts.RetryPolicy
   649  		}
   650  	}
   651  	return
   652  }
   653  
   654  func doFallbackIfNeeded(ctx context.Context, ri rpcinfo.RPCInfo, request, response interface{}, oriErr error, cliFallback *fallback.Policy, callOpts *callopt.CallOptions) (err, reportErr error) {
   655  	fallback, hasFallback := getFallbackPolicy(cliFallback, callOpts)
   656  	err = oriErr
   657  	reportErr = oriErr
   658  	var fbErr error
   659  	if hasFallback {
   660  		reportAsFB := false
   661  		// Notice: If rpc err is nil, rpcStatAsFB will always be false, even if it's set to true by user.
   662  		fbErr, reportAsFB = fallback.DoIfNeeded(ctx, ri, request, response, oriErr)
   663  		if reportAsFB {
   664  			reportErr = fbErr
   665  		}
   666  		err = fbErr
   667  	}
   668  
   669  	if err == nil && !hasFallback {
   670  		err = ri.Invocation().BizStatusErr()
   671  	}
   672  	return
   673  }
   674  
   675  // return fallback policy from call option and client option.
   676  func getFallbackPolicy(cliOptFB *fallback.Policy, callOpts *callopt.CallOptions) (fb *fallback.Policy, hasFallback bool) {
   677  	var callOptFB *fallback.Policy
   678  	if callOpts != nil {
   679  		callOptFB = callOpts.Fallback
   680  	}
   681  	if callOptFB != nil {
   682  		return callOptFB, true
   683  	}
   684  	if cliOptFB != nil {
   685  		return cliOptFB, true
   686  	}
   687  	return nil, false
   688  }
   689  
   690  func initRPCInfo(ctx context.Context, method string, opt *client.Options, svcInfo *serviceinfo.ServiceInfo, retryTimes int, firstRI rpcinfo.RPCInfo) (context.Context, rpcinfo.RPCInfo, *callopt.CallOptions) {
   691  	cfg := rpcinfo.AsMutableRPCConfig(opt.Configs).Clone()
   692  	rmt := remoteinfo.NewRemoteInfo(opt.Svr, method)
   693  	var callOpts *callopt.CallOptions
   694  	ctx, callOpts = applyCallOptions(ctx, cfg, rmt, opt)
   695  	var rpcStats rpcinfo.MutableRPCStats
   696  	if firstRI != nil {
   697  		rpcStats = rpcinfo.AsMutableRPCStats(firstRI.Stats().CopyForRetry())
   698  	} else {
   699  		rpcStats = rpcinfo.AsMutableRPCStats(rpcinfo.NewRPCStats())
   700  	}
   701  	if opt.StatsLevel != nil {
   702  		rpcStats.SetLevel(*opt.StatsLevel)
   703  	}
   704  
   705  	mi := svcInfo.MethodInfo(method)
   706  	if mi != nil && mi.OneWay() {
   707  		cfg.SetInteractionMode(rpcinfo.Oneway)
   708  	}
   709  	if retryTimes > 0 {
   710  		// it is used to distinguish the request is a local retry request.
   711  		rmt.SetTag(rpcinfo.RetryTag, strconv.Itoa(retryTimes))
   712  	}
   713  
   714  	// Export read-only views to external users.
   715  	ri := rpcinfo.NewRPCInfo(
   716  		rpcinfo.FromBasicInfo(opt.Cli),
   717  		rmt.ImmutableView(),
   718  		rpcinfo.NewInvocation(svcInfo.ServiceName, method, svcInfo.GetPackageName()),
   719  		cfg.ImmutableView(),
   720  		rpcStats.ImmutableView(),
   721  	)
   722  
   723  	if fromMethod := ctx.Value(consts.CtxKeyMethod); fromMethod != nil {
   724  		rpcinfo.AsMutableEndpointInfo(ri.From()).SetMethod(fromMethod.(string))
   725  	}
   726  
   727  	if p := opt.Timeouts; p != nil {
   728  		if c := p.Timeouts(ri); c != nil {
   729  			_ = cfg.SetRPCTimeout(c.RPCTimeout())
   730  			_ = cfg.SetConnectTimeout(c.ConnectTimeout())
   731  			_ = cfg.SetReadWriteTimeout(c.ReadWriteTimeout())
   732  		}
   733  	}
   734  
   735  	ctx = rpcinfo.NewCtxWithRPCInfo(ctx, ri)
   736  
   737  	if callOpts != nil && callOpts.CompressorName != "" {
   738  		// set send grpc compressor at client to tell how to server decode
   739  		remote.SetSendCompressor(ri, callOpts.CompressorName)
   740  	}
   741  
   742  	return ctx, ri, callOpts
   743  }