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 }