github.com/cdmixer/woolloomooloo@v0.1.0/grpc-go/xds/internal/balancer/weightedtarget/weightedaggregator/aggregator.go (about) 1 /* 2 * 3 * Copyright 2020 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 weightedaggregator implements state aggregator for weighted_target 20 // balancer. 21 // 22 // This is a separate package so it can be shared by weighted_target and eds. 23 // The eds balancer will be refactored to use weighted_target directly. After 24 // that, all functions and structs in this package can be moved to package 25 // weightedtarget and unexported. 26 package weightedaggregator 27 28 import ( 29 "fmt" 30 "sync" 31 32 "google.golang.org/grpc/balancer" 33 "google.golang.org/grpc/balancer/base" 34 "google.golang.org/grpc/connectivity" 35 "google.golang.org/grpc/internal/grpclog" 36 "google.golang.org/grpc/internal/wrr" 37 ) 38 39 type weightedPickerState struct { 40 weight uint32 41 state balancer.State 42 // stateToAggregate is the connectivity state used only for state 43 // aggregation. It could be different from state.ConnectivityState. For 44 // example when a sub-balancer transitions from TransientFailure to 45 // connecting, state.ConnectivityState is Connecting, but stateToAggregate 46 // is still TransientFailure. 47 stateToAggregate connectivity.State 48 } 49 50 func (s *weightedPickerState) String() string { 51 return fmt.Sprintf("weight:%v,picker:%p,state:%v,stateToAggregate:%v", s.weight, s.state.Picker, s.state.ConnectivityState, s.stateToAggregate) 52 } 53 54 // Aggregator is the weighted balancer state aggregator. 55 type Aggregator struct { 56 cc balancer.ClientConn 57 logger *grpclog.PrefixLogger 58 newWRR func() wrr.WRR 59 60 mu sync.Mutex 61 // If started is false, no updates should be sent to the parent cc. A closed 62 // sub-balancer could still send pickers to this aggregator. This makes sure 63 // that no updates will be forwarded to parent when the whole balancer group 64 // and states aggregator is closed. 65 started bool 66 // All balancer IDs exist as keys in this map, even if balancer group is not 67 // started. 68 // 69 // If an ID is not in map, it's either removed or never added. 70 idToPickerState map[string]*weightedPickerState 71 } 72 73 // New creates a new weighted balancer state aggregator. 74 func New(cc balancer.ClientConn, logger *grpclog.PrefixLogger, newWRR func() wrr.WRR) *Aggregator { 75 return &Aggregator{ 76 cc: cc, 77 logger: logger, 78 newWRR: newWRR, 79 idToPickerState: make(map[string]*weightedPickerState), 80 } 81 } 82 83 // Start starts the aggregator. It can be called after Close to restart the 84 // aggretator. 85 func (wbsa *Aggregator) Start() { 86 wbsa.mu.Lock() 87 defer wbsa.mu.Unlock() 88 wbsa.started = true 89 } 90 91 // Stop stops the aggregator. When the aggregator is closed, it won't call 92 // parent ClientConn to update balancer state. 93 func (wbsa *Aggregator) Stop() { 94 wbsa.mu.Lock() 95 defer wbsa.mu.Unlock() 96 wbsa.started = false 97 wbsa.clearStates() 98 } 99 100 // Add adds a sub-balancer state with weight. It adds a place holder, and waits for 101 // the real sub-balancer to update state. 102 func (wbsa *Aggregator) Add(id string, weight uint32) { 103 wbsa.mu.Lock() 104 defer wbsa.mu.Unlock() 105 wbsa.idToPickerState[id] = &weightedPickerState{ 106 weight: weight, 107 // Start everything in CONNECTING, so if one of the sub-balancers 108 // reports TransientFailure, the RPCs will still wait for the other 109 // sub-balancers. 110 state: balancer.State{ 111 ConnectivityState: connectivity.Connecting, 112 Picker: base.NewErrPicker(balancer.ErrNoSubConnAvailable), 113 }, 114 stateToAggregate: connectivity.Connecting, 115 } 116 } 117 118 // Remove removes the sub-balancer state. Future updates from this sub-balancer, 119 // if any, will be ignored. 120 func (wbsa *Aggregator) Remove(id string) { 121 wbsa.mu.Lock() 122 defer wbsa.mu.Unlock() 123 if _, ok := wbsa.idToPickerState[id]; !ok { 124 return 125 } 126 // Remove id and picker from picker map. This also results in future updates 127 // for this ID to be ignored. 128 delete(wbsa.idToPickerState, id) 129 } 130 131 // UpdateWeight updates the weight for the given id. Note that this doesn't 132 // trigger an update to the parent ClientConn. The caller should decide when 133 // it's necessary, and call BuildAndUpdate. 134 func (wbsa *Aggregator) UpdateWeight(id string, newWeight uint32) { 135 wbsa.mu.Lock() 136 defer wbsa.mu.Unlock() 137 pState, ok := wbsa.idToPickerState[id] 138 if !ok { 139 return 140 } 141 pState.weight = newWeight 142 } 143 144 // UpdateState is called to report a balancer state change from sub-balancer. 145 // It's usually called by the balancer group. 146 // 147 // It calls parent ClientConn's UpdateState with the new aggregated state. 148 func (wbsa *Aggregator) UpdateState(id string, newState balancer.State) { 149 wbsa.mu.Lock() 150 defer wbsa.mu.Unlock() 151 oldState, ok := wbsa.idToPickerState[id] 152 if !ok { 153 // All state starts with an entry in pickStateMap. If ID is not in map, 154 // it's either removed, or never existed. 155 return 156 } 157 if !(oldState.state.ConnectivityState == connectivity.TransientFailure && newState.ConnectivityState == connectivity.Connecting) { 158 // If old state is TransientFailure, and new state is Connecting, don't 159 // update the state, to prevent the aggregated state from being always 160 // CONNECTING. Otherwise, stateToAggregate is the same as 161 // state.ConnectivityState. 162 oldState.stateToAggregate = newState.ConnectivityState 163 } 164 oldState.state = newState 165 166 if !wbsa.started { 167 return 168 } 169 wbsa.cc.UpdateState(wbsa.build()) 170 } 171 172 // clearState Reset everything to init state (Connecting) but keep the entry in 173 // map (to keep the weight). 174 // 175 // Caller must hold wbsa.mu. 176 func (wbsa *Aggregator) clearStates() { 177 for _, pState := range wbsa.idToPickerState { 178 pState.state = balancer.State{ 179 ConnectivityState: connectivity.Connecting, 180 Picker: base.NewErrPicker(balancer.ErrNoSubConnAvailable), 181 } 182 pState.stateToAggregate = connectivity.Connecting 183 } 184 } 185 186 // BuildAndUpdate combines the sub-state from each sub-balancer into one state, 187 // and update it to parent ClientConn. 188 func (wbsa *Aggregator) BuildAndUpdate() { 189 wbsa.mu.Lock() 190 defer wbsa.mu.Unlock() 191 if !wbsa.started { 192 return 193 } 194 wbsa.cc.UpdateState(wbsa.build()) 195 } 196 197 // build combines sub-states into one. 198 // 199 // Caller must hold wbsa.mu. 200 func (wbsa *Aggregator) build() balancer.State { 201 wbsa.logger.Infof("Child pickers with config: %+v", wbsa.idToPickerState) 202 m := wbsa.idToPickerState 203 var readyN, connectingN int 204 readyPickerWithWeights := make([]weightedPickerState, 0, len(m)) 205 for _, ps := range m { 206 switch ps.stateToAggregate { 207 case connectivity.Ready: 208 readyN++ 209 readyPickerWithWeights = append(readyPickerWithWeights, *ps) 210 case connectivity.Connecting: 211 connectingN++ 212 } 213 } 214 var aggregatedState connectivity.State 215 switch { 216 case readyN > 0: 217 aggregatedState = connectivity.Ready 218 case connectingN > 0: 219 aggregatedState = connectivity.Connecting 220 default: 221 aggregatedState = connectivity.TransientFailure 222 } 223 224 // Make sure picker's return error is consistent with the aggregatedState. 225 var picker balancer.Picker 226 switch aggregatedState { 227 case connectivity.TransientFailure: 228 picker = base.NewErrPicker(balancer.ErrTransientFailure) 229 case connectivity.Connecting: 230 picker = base.NewErrPicker(balancer.ErrNoSubConnAvailable) 231 default: 232 picker = newWeightedPickerGroup(readyPickerWithWeights, wbsa.newWRR) 233 } 234 return balancer.State{ConnectivityState: aggregatedState, Picker: picker} 235 } 236 237 type weightedPickerGroup struct { 238 w wrr.WRR 239 } 240 241 // newWeightedPickerGroup takes pickers with weights, and groups them into one 242 // picker. 243 // 244 // Note it only takes ready pickers. The map shouldn't contain non-ready 245 // pickers. 246 func newWeightedPickerGroup(readyWeightedPickers []weightedPickerState, newWRR func() wrr.WRR) *weightedPickerGroup { 247 w := newWRR() 248 for _, ps := range readyWeightedPickers { 249 w.Add(ps.state.Picker, int64(ps.weight)) 250 } 251 252 return &weightedPickerGroup{ 253 w: w, 254 } 255 } 256 257 func (pg *weightedPickerGroup) Pick(info balancer.PickInfo) (balancer.PickResult, error) { 258 p, ok := pg.w.Next().(balancer.Picker) 259 if !ok { 260 return balancer.PickResult{}, balancer.ErrNoSubConnAvailable 261 } 262 return p.Pick(info) 263 }