google.golang.org/grpc@v1.72.2/balancer/lazy/lazy.go (about) 1 /* 2 * 3 * Copyright 2025 gRPC authors. 4 * 5 * Licensed under the Apache License, Version 2.0 (the "License"); 6 * you may not use this file except in compliance with the License. 7 * You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 * 17 */ 18 19 // Package lazy contains a load balancer that starts in IDLE instead of 20 // CONNECTING. Once it starts connecting, it instantiates its delegate. 21 // 22 // # Experimental 23 // 24 // Notice: This package is EXPERIMENTAL and may be changed or removed in a 25 // later release. 26 package lazy 27 28 import ( 29 "fmt" 30 "sync" 31 32 "google.golang.org/grpc/balancer" 33 "google.golang.org/grpc/connectivity" 34 "google.golang.org/grpc/grpclog" 35 "google.golang.org/grpc/resolver" 36 37 internalgrpclog "google.golang.org/grpc/internal/grpclog" 38 ) 39 40 var ( 41 logger = grpclog.Component("lazy-lb") 42 ) 43 44 const ( 45 logPrefix = "[lazy-lb %p] " 46 ) 47 48 // ChildBuilderFunc creates a new balancer with the ClientConn. It has the same 49 // type as the balancer.Builder.Build method. 50 type ChildBuilderFunc func(cc balancer.ClientConn, opts balancer.BuildOptions) balancer.Balancer 51 52 // NewBalancer is the constructor for the lazy balancer. 53 func NewBalancer(cc balancer.ClientConn, bOpts balancer.BuildOptions, childBuilder ChildBuilderFunc) balancer.Balancer { 54 b := &lazyBalancer{ 55 cc: cc, 56 buildOptions: bOpts, 57 childBuilder: childBuilder, 58 } 59 b.logger = internalgrpclog.NewPrefixLogger(logger, fmt.Sprintf(logPrefix, b)) 60 cc.UpdateState(balancer.State{ 61 ConnectivityState: connectivity.Idle, 62 Picker: &idlePicker{exitIdle: sync.OnceFunc(func() { 63 // Call ExitIdle in a new goroutine to avoid deadlocks while calling 64 // back into the channel synchronously. 65 go b.ExitIdle() 66 })}, 67 }) 68 return b 69 } 70 71 type lazyBalancer struct { 72 // The following fields are initialized at build time and read-only after 73 // that and therefore do not need to be guarded by a mutex. 74 cc balancer.ClientConn 75 buildOptions balancer.BuildOptions 76 logger *internalgrpclog.PrefixLogger 77 childBuilder ChildBuilderFunc 78 79 // The following fields are accessed while handling calls to the idlePicker 80 // and when handling ClientConn state updates. They are guarded by a mutex. 81 82 mu sync.Mutex 83 delegate balancer.Balancer 84 latestClientConnState *balancer.ClientConnState 85 latestResolverError error 86 } 87 88 func (lb *lazyBalancer) Close() { 89 lb.mu.Lock() 90 defer lb.mu.Unlock() 91 if lb.delegate != nil { 92 lb.delegate.Close() 93 lb.delegate = nil 94 } 95 } 96 97 func (lb *lazyBalancer) ResolverError(err error) { 98 lb.mu.Lock() 99 defer lb.mu.Unlock() 100 if lb.delegate != nil { 101 lb.delegate.ResolverError(err) 102 return 103 } 104 lb.latestResolverError = err 105 } 106 107 func (lb *lazyBalancer) UpdateClientConnState(ccs balancer.ClientConnState) error { 108 lb.mu.Lock() 109 defer lb.mu.Unlock() 110 if lb.delegate != nil { 111 return lb.delegate.UpdateClientConnState(ccs) 112 } 113 114 lb.latestClientConnState = &ccs 115 lb.latestResolverError = nil 116 return nil 117 } 118 119 // UpdateSubConnState implements balancer.Balancer. 120 func (lb *lazyBalancer) UpdateSubConnState(balancer.SubConn, balancer.SubConnState) { 121 // UpdateSubConnState is deprecated. 122 } 123 124 func (lb *lazyBalancer) ExitIdle() { 125 lb.mu.Lock() 126 defer lb.mu.Unlock() 127 if lb.delegate != nil { 128 if d, ok := lb.delegate.(balancer.ExitIdler); ok { 129 d.ExitIdle() 130 } 131 return 132 } 133 lb.delegate = lb.childBuilder(lb.cc, lb.buildOptions) 134 if lb.latestClientConnState != nil { 135 if err := lb.delegate.UpdateClientConnState(*lb.latestClientConnState); err != nil { 136 if err == balancer.ErrBadResolverState { 137 lb.cc.ResolveNow(resolver.ResolveNowOptions{}) 138 } else { 139 lb.logger.Warningf("Error from child policy on receiving initial state: %v", err) 140 } 141 } 142 lb.latestClientConnState = nil 143 } 144 if lb.latestResolverError != nil { 145 lb.delegate.ResolverError(lb.latestResolverError) 146 lb.latestResolverError = nil 147 } 148 } 149 150 // idlePicker is used when the SubConn is IDLE and kicks the SubConn into 151 // CONNECTING when Pick is called. 152 type idlePicker struct { 153 exitIdle func() 154 } 155 156 func (i *idlePicker) Pick(balancer.PickInfo) (balancer.PickResult, error) { 157 i.exitIdle() 158 return balancer.PickResult{}, balancer.ErrNoSubConnAvailable 159 }