google.golang.org/grpc@v1.72.2/xds/internal/balancer/outlierdetection/balancer.go (about) 1 /* 2 * 3 * Copyright 2022 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 outlierdetection provides an implementation of the outlier detection 20 // LB policy, as defined in 21 // https://github.com/grpc/proposal/blob/master/A50-xds-outlier-detection.md. 22 package outlierdetection 23 24 import ( 25 "encoding/json" 26 "fmt" 27 "math" 28 rand "math/rand/v2" 29 "strings" 30 "sync" 31 "sync/atomic" 32 "time" 33 34 "google.golang.org/grpc/balancer" 35 "google.golang.org/grpc/balancer/pickfirst/pickfirstleaf" 36 "google.golang.org/grpc/connectivity" 37 "google.golang.org/grpc/internal/balancer/gracefulswitch" 38 "google.golang.org/grpc/internal/buffer" 39 "google.golang.org/grpc/internal/channelz" 40 "google.golang.org/grpc/internal/grpclog" 41 "google.golang.org/grpc/internal/grpcsync" 42 iserviceconfig "google.golang.org/grpc/internal/serviceconfig" 43 "google.golang.org/grpc/resolver" 44 "google.golang.org/grpc/serviceconfig" 45 ) 46 47 // Globals to stub out in tests. 48 var ( 49 afterFunc = time.AfterFunc 50 now = time.Now 51 ) 52 53 // Name is the name of the outlier detection balancer. 54 const Name = "outlier_detection_experimental" 55 56 func init() { 57 balancer.Register(bb{}) 58 } 59 60 type bb struct{} 61 62 func (bb) Build(cc balancer.ClientConn, bOpts balancer.BuildOptions) balancer.Balancer { 63 b := &outlierDetectionBalancer{ 64 ClientConn: cc, 65 closed: grpcsync.NewEvent(), 66 done: grpcsync.NewEvent(), 67 addrs: make(map[string]*endpointInfo), 68 scUpdateCh: buffer.NewUnbounded(), 69 pickerUpdateCh: buffer.NewUnbounded(), 70 channelzParent: bOpts.ChannelzParent, 71 endpoints: resolver.NewEndpointMap[*endpointInfo](), 72 } 73 b.logger = prefixLogger(b) 74 b.logger.Infof("Created") 75 b.child = synchronizingBalancerWrapper{lb: gracefulswitch.NewBalancer(b, bOpts)} 76 go b.run() 77 return b 78 } 79 80 func (bb) ParseConfig(s json.RawMessage) (serviceconfig.LoadBalancingConfig, error) { 81 lbCfg := &LBConfig{ 82 // Default top layer values as documented in A50. 83 Interval: iserviceconfig.Duration(10 * time.Second), 84 BaseEjectionTime: iserviceconfig.Duration(30 * time.Second), 85 MaxEjectionTime: iserviceconfig.Duration(300 * time.Second), 86 MaxEjectionPercent: 10, 87 } 88 89 // This unmarshalling handles underlying layers sre and fpe which have their 90 // own defaults for their fields if either sre or fpe are present. 91 if err := json.Unmarshal(s, lbCfg); err != nil { // Validates child config if present as well. 92 return nil, fmt.Errorf("xds: unable to unmarshal LBconfig: %s, error: %v", string(s), err) 93 } 94 95 // Note: in the xds flow, these validations will never fail. The xdsclient 96 // performs the same validations as here on the xds Outlier Detection 97 // resource before parsing resource into JSON which this function gets 98 // called with. A50 defines two separate places for these validations to 99 // take place, the xdsclient and this ParseConfig method. "When parsing a 100 // config from JSON, if any of these requirements is violated, that should 101 // be treated as a parsing error." - A50 102 switch { 103 // "The google.protobuf.Duration fields interval, base_ejection_time, and 104 // max_ejection_time must obey the restrictions in the 105 // google.protobuf.Duration documentation and they must have non-negative 106 // values." - A50 107 // Approximately 290 years is the maximum time that time.Duration (int64) 108 // can represent. The restrictions on the protobuf.Duration field are to be 109 // within +-10000 years. Thus, just check for negative values. 110 case lbCfg.Interval < 0: 111 return nil, fmt.Errorf("OutlierDetectionLoadBalancingConfig.interval = %s; must be >= 0", lbCfg.Interval) 112 case lbCfg.BaseEjectionTime < 0: 113 return nil, fmt.Errorf("OutlierDetectionLoadBalancingConfig.base_ejection_time = %s; must be >= 0", lbCfg.BaseEjectionTime) 114 case lbCfg.MaxEjectionTime < 0: 115 return nil, fmt.Errorf("OutlierDetectionLoadBalancingConfig.max_ejection_time = %s; must be >= 0", lbCfg.MaxEjectionTime) 116 117 // "The fields max_ejection_percent, 118 // success_rate_ejection.enforcement_percentage, 119 // failure_percentage_ejection.threshold, and 120 // failure_percentage.enforcement_percentage must have values less than or 121 // equal to 100." - A50 122 case lbCfg.MaxEjectionPercent > 100: 123 return nil, fmt.Errorf("OutlierDetectionLoadBalancingConfig.max_ejection_percent = %v; must be <= 100", lbCfg.MaxEjectionPercent) 124 case lbCfg.SuccessRateEjection != nil && lbCfg.SuccessRateEjection.EnforcementPercentage > 100: 125 return nil, fmt.Errorf("OutlierDetectionLoadBalancingConfig.SuccessRateEjection.enforcement_percentage = %v; must be <= 100", lbCfg.SuccessRateEjection.EnforcementPercentage) 126 case lbCfg.FailurePercentageEjection != nil && lbCfg.FailurePercentageEjection.Threshold > 100: 127 return nil, fmt.Errorf("OutlierDetectionLoadBalancingConfig.FailurePercentageEjection.threshold = %v; must be <= 100", lbCfg.FailurePercentageEjection.Threshold) 128 case lbCfg.FailurePercentageEjection != nil && lbCfg.FailurePercentageEjection.EnforcementPercentage > 100: 129 return nil, fmt.Errorf("OutlierDetectionLoadBalancingConfig.FailurePercentageEjection.enforcement_percentage = %v; must be <= 100", lbCfg.FailurePercentageEjection.EnforcementPercentage) 130 } 131 return lbCfg, nil 132 } 133 134 func (bb) Name() string { 135 return Name 136 } 137 138 // scUpdate wraps a subConn update to be sent to the child balancer. 139 type scUpdate struct { 140 scw *subConnWrapper 141 state balancer.SubConnState 142 } 143 144 type ejectionUpdate struct { 145 scw *subConnWrapper 146 isEjected bool // true for ejected, false for unejected 147 } 148 149 type lbCfgUpdate struct { 150 lbCfg *LBConfig 151 // to make sure picker is updated synchronously. 152 done chan struct{} 153 } 154 155 type scHealthUpdate struct { 156 scw *subConnWrapper 157 state balancer.SubConnState 158 } 159 160 type outlierDetectionBalancer struct { 161 balancer.ClientConn 162 // These fields are safe to be accessed without holding any mutex because 163 // they are synchronized in run(), which makes these field accesses happen 164 // serially. 165 // 166 // childState is the latest balancer state received from the child. 167 childState balancer.State 168 // recentPickerNoop represents whether the most recent picker sent upward to 169 // the balancer.ClientConn is a noop picker, which doesn't count RPC's. Used 170 // to suppress redundant picker updates. 171 recentPickerNoop bool 172 173 closed *grpcsync.Event 174 done *grpcsync.Event 175 logger *grpclog.PrefixLogger 176 channelzParent channelz.Identifier 177 178 child synchronizingBalancerWrapper 179 180 // mu guards access to the following fields. It also helps to synchronize 181 // behaviors of the following events: config updates, firing of the interval 182 // timer, SubConn State updates, SubConn address updates, and child state 183 // updates. 184 // 185 // For example, when we receive a config update in the middle of the 186 // interval timer algorithm, which uses knobs present in the config, the 187 // balancer will wait for the interval timer algorithm to finish before 188 // persisting the new configuration. 189 // 190 // Another example would be the updating of the endpoints or addrs map, such 191 // as from a SubConn address update in the middle of the interval timer 192 // algorithm which uses endpoints. This balancer waits for the interval 193 // timer algorithm to finish before making the update to the endpoints map. 194 // 195 // This mutex is never held when calling methods on the child policy 196 // (within the context of a single goroutine). 197 mu sync.Mutex 198 // endpoints stores pointers to endpointInfo objects for each endpoint. 199 endpoints *resolver.EndpointMap[*endpointInfo] 200 // addrs stores pointers to endpointInfo objects for each address. Addresses 201 // belonging to the same endpoint point to the same object. 202 addrs map[string]*endpointInfo 203 cfg *LBConfig 204 timerStartTime time.Time 205 intervalTimer *time.Timer 206 inhibitPickerUpdates bool 207 updateUnconditionally bool 208 numEndpointsEjected int // For fast calculations of percentage of endpoints ejected 209 210 scUpdateCh *buffer.Unbounded 211 pickerUpdateCh *buffer.Unbounded 212 } 213 214 // noopConfig returns whether this balancer is configured with a logical no-op 215 // configuration or not. 216 // 217 // Caller must hold b.mu. 218 func (b *outlierDetectionBalancer) noopConfig() bool { 219 return b.cfg.SuccessRateEjection == nil && b.cfg.FailurePercentageEjection == nil 220 } 221 222 // onIntervalConfig handles logic required specifically on the receipt of a 223 // configuration which specifies to count RPC's and periodically perform passive 224 // health checking based on heuristics defined in configuration every configured 225 // interval. 226 // 227 // Caller must hold b.mu. 228 func (b *outlierDetectionBalancer) onIntervalConfig() { 229 var interval time.Duration 230 if b.timerStartTime.IsZero() { 231 b.timerStartTime = time.Now() 232 for _, epInfo := range b.endpoints.Values() { 233 epInfo.callCounter.clear() 234 } 235 interval = time.Duration(b.cfg.Interval) 236 } else { 237 interval = time.Duration(b.cfg.Interval) - now().Sub(b.timerStartTime) 238 if interval < 0 { 239 interval = 0 240 } 241 } 242 b.intervalTimer = afterFunc(interval, b.intervalTimerAlgorithm) 243 } 244 245 // onNoopConfig handles logic required specifically on the receipt of a 246 // configuration which specifies the balancer to be a noop. 247 // 248 // Caller must hold b.mu. 249 func (b *outlierDetectionBalancer) onNoopConfig() { 250 // "If a config is provided with both the `success_rate_ejection` and 251 // `failure_percentage_ejection` fields unset, skip starting the timer and 252 // do the following:" 253 // "Unset the timer start timestamp." 254 b.timerStartTime = time.Time{} 255 for _, epInfo := range b.endpoints.Values() { 256 // "Uneject all currently ejected endpoints." 257 if !epInfo.latestEjectionTimestamp.IsZero() { 258 b.unejectEndpoint(epInfo) 259 } 260 // "Reset each endpoint's ejection time multiplier to 0." 261 epInfo.ejectionTimeMultiplier = 0 262 } 263 } 264 265 func (b *outlierDetectionBalancer) UpdateClientConnState(s balancer.ClientConnState) error { 266 lbCfg, ok := s.BalancerConfig.(*LBConfig) 267 if !ok { 268 b.logger.Errorf("received config with unexpected type %T: %v", s.BalancerConfig, s.BalancerConfig) 269 return balancer.ErrBadResolverState 270 } 271 272 // Reject whole config if child policy doesn't exist, don't persist it for 273 // later. 274 bb := balancer.Get(lbCfg.ChildPolicy.Name) 275 if bb == nil { 276 return fmt.Errorf("outlier detection: child balancer %q not registered", lbCfg.ChildPolicy.Name) 277 } 278 279 // It is safe to read b.cfg here without holding the mutex, as the only 280 // write to b.cfg happens later in this function. This function is part of 281 // the balancer.Balancer API, so it is guaranteed to be called in a 282 // synchronous manner, so it cannot race with this read. 283 if b.cfg == nil || b.cfg.ChildPolicy.Name != lbCfg.ChildPolicy.Name { 284 if err := b.child.switchTo(bb); err != nil { 285 return fmt.Errorf("outlier detection: error switching to child of type %q: %v", lbCfg.ChildPolicy.Name, err) 286 } 287 } 288 289 b.mu.Lock() 290 // Inhibit child picker updates until this UpdateClientConnState() call 291 // completes. If needed, a picker update containing the no-op config bit 292 // determined from this config and most recent state from the child will be 293 // sent synchronously upward at the end of this UpdateClientConnState() 294 // call. 295 b.inhibitPickerUpdates = true 296 b.updateUnconditionally = false 297 b.cfg = lbCfg 298 299 newEndpoints := resolver.NewEndpointMap[bool]() 300 for _, ep := range s.ResolverState.Endpoints { 301 newEndpoints.Set(ep, true) 302 if _, ok := b.endpoints.Get(ep); !ok { 303 b.endpoints.Set(ep, newEndpointInfo()) 304 } 305 } 306 307 for _, ep := range b.endpoints.Keys() { 308 if _, ok := newEndpoints.Get(ep); !ok { 309 b.endpoints.Delete(ep) 310 } 311 } 312 313 // populate the addrs map. 314 b.addrs = map[string]*endpointInfo{} 315 for _, ep := range s.ResolverState.Endpoints { 316 epInfo, _ := b.endpoints.Get(ep) 317 for _, addr := range ep.Addresses { 318 if _, ok := b.addrs[addr.Addr]; ok { 319 b.logger.Errorf("Endpoints contain duplicate address %q", addr.Addr) 320 continue 321 } 322 b.addrs[addr.Addr] = epInfo 323 } 324 } 325 326 if b.intervalTimer != nil { 327 b.intervalTimer.Stop() 328 } 329 330 if b.noopConfig() { 331 b.onNoopConfig() 332 } else { 333 b.onIntervalConfig() 334 } 335 b.mu.Unlock() 336 337 err := b.child.updateClientConnState(balancer.ClientConnState{ 338 ResolverState: s.ResolverState, 339 BalancerConfig: b.cfg.ChildPolicy.Config, 340 }) 341 342 done := make(chan struct{}) 343 b.pickerUpdateCh.Put(lbCfgUpdate{ 344 lbCfg: lbCfg, 345 done: done, 346 }) 347 <-done 348 349 return err 350 } 351 352 func (b *outlierDetectionBalancer) ResolverError(err error) { 353 b.child.resolverError(err) 354 } 355 356 func (b *outlierDetectionBalancer) updateSubConnState(scw *subConnWrapper, state balancer.SubConnState) { 357 b.mu.Lock() 358 defer b.mu.Unlock() 359 scw.setLatestConnectivityState(state.ConnectivityState) 360 b.scUpdateCh.Put(&scUpdate{ 361 scw: scw, 362 state: state, 363 }) 364 } 365 366 func (b *outlierDetectionBalancer) UpdateSubConnState(sc balancer.SubConn, state balancer.SubConnState) { 367 b.logger.Errorf("UpdateSubConnState(%v, %+v) called unexpectedly", sc, state) 368 } 369 370 func (b *outlierDetectionBalancer) Close() { 371 b.closed.Fire() 372 <-b.done.Done() 373 b.child.closeLB() 374 375 b.scUpdateCh.Close() 376 b.pickerUpdateCh.Close() 377 378 b.mu.Lock() 379 defer b.mu.Unlock() 380 if b.intervalTimer != nil { 381 b.intervalTimer.Stop() 382 } 383 } 384 385 func (b *outlierDetectionBalancer) ExitIdle() { 386 b.child.exitIdle() 387 } 388 389 // wrappedPicker delegates to the child policy's picker, and when the request 390 // finishes, it increments the corresponding counter in the map entry referenced 391 // by the subConnWrapper that was picked. If both the `success_rate_ejection` 392 // and `failure_percentage_ejection` fields are unset in the configuration, this 393 // picker will not count. 394 type wrappedPicker struct { 395 childPicker balancer.Picker 396 noopPicker bool 397 } 398 399 func (wp *wrappedPicker) Pick(info balancer.PickInfo) (balancer.PickResult, error) { 400 pr, err := wp.childPicker.Pick(info) 401 if err != nil { 402 return balancer.PickResult{}, err 403 } 404 405 done := func(di balancer.DoneInfo) { 406 if !wp.noopPicker { 407 incrementCounter(pr.SubConn, di) 408 } 409 if pr.Done != nil { 410 pr.Done(di) 411 } 412 } 413 scw, ok := pr.SubConn.(*subConnWrapper) 414 if !ok { 415 // This can never happen, but check is present for defensive 416 // programming. 417 logger.Errorf("Picked SubConn from child picker is not a SubConnWrapper") 418 return balancer.PickResult{ 419 SubConn: pr.SubConn, 420 Done: done, 421 Metadata: pr.Metadata, 422 }, nil 423 } 424 return balancer.PickResult{ 425 SubConn: scw.SubConn, 426 Done: done, 427 Metadata: pr.Metadata, 428 }, nil 429 } 430 431 func incrementCounter(sc balancer.SubConn, info balancer.DoneInfo) { 432 scw, ok := sc.(*subConnWrapper) 433 if !ok { 434 // Shouldn't happen, as comes from child 435 return 436 } 437 438 // scw.endpointInfo and callCounter.activeBucket can be written to 439 // concurrently (the pointers themselves). Thus, protect the reads here with 440 // atomics to prevent data corruption. There exists a race in which you read 441 // the endpointInfo or active bucket pointer and then that pointer points to 442 // deprecated memory. If this goroutine yields the processor, in between 443 // reading the endpointInfo pointer and writing to the active bucket, 444 // UpdateAddresses can switch the endpointInfo the scw points to. Writing to 445 // an outdated endpoint is a very small race and tolerable. After reading 446 // callCounter.activeBucket in this picker a swap call can concurrently 447 // change what activeBucket points to. A50 says to swap the pointer, which 448 // will cause this race to write to deprecated memory the interval timer 449 // algorithm will never read, which makes this race alright. 450 epInfo := scw.endpointInfo.Load() 451 if epInfo == nil { 452 return 453 } 454 ab := epInfo.callCounter.activeBucket.Load() 455 456 if info.Err == nil { 457 atomic.AddUint32(&ab.numSuccesses, 1) 458 } else { 459 atomic.AddUint32(&ab.numFailures, 1) 460 } 461 } 462 463 func (b *outlierDetectionBalancer) UpdateState(s balancer.State) { 464 b.pickerUpdateCh.Put(s) 465 } 466 467 func (b *outlierDetectionBalancer) NewSubConn(addrs []resolver.Address, opts balancer.NewSubConnOptions) (balancer.SubConn, error) { 468 oldListener := opts.StateListener 469 scw := &subConnWrapper{ 470 addresses: addrs, 471 scUpdateCh: b.scUpdateCh, 472 listener: oldListener, 473 latestRawConnectivityState: balancer.SubConnState{ConnectivityState: connectivity.Idle}, 474 latestHealthState: balancer.SubConnState{ConnectivityState: connectivity.Connecting}, 475 healthListenerEnabled: len(addrs) == 1 && pickfirstleaf.IsManagedByPickfirst(addrs[0]), 476 } 477 opts.StateListener = func(state balancer.SubConnState) { b.updateSubConnState(scw, state) } 478 b.mu.Lock() 479 defer b.mu.Unlock() 480 sc, err := b.ClientConn.NewSubConn(addrs, opts) 481 if err != nil { 482 return nil, err 483 } 484 scw.SubConn = sc 485 if len(addrs) != 1 { 486 return scw, nil 487 } 488 epInfo, ok := b.addrs[addrs[0].Addr] 489 if !ok { 490 return scw, nil 491 } 492 epInfo.sws = append(epInfo.sws, scw) 493 scw.endpointInfo.Store(epInfo) 494 if !epInfo.latestEjectionTimestamp.IsZero() { 495 scw.eject() 496 } 497 return scw, nil 498 } 499 500 func (b *outlierDetectionBalancer) RemoveSubConn(sc balancer.SubConn) { 501 b.logger.Errorf("RemoveSubConn(%v) called unexpectedly", sc) 502 } 503 504 // appendIfPresent appends the scw to the endpoint, if the address is present in 505 // the Outlier Detection balancers address map. Returns nil if not present, and 506 // the map entry if present. 507 // 508 // Caller must hold b.mu. 509 func (b *outlierDetectionBalancer) appendIfPresent(addr string, scw *subConnWrapper) *endpointInfo { 510 epInfo, ok := b.addrs[addr] 511 if !ok { 512 return nil 513 } 514 515 epInfo.sws = append(epInfo.sws, scw) 516 scw.endpointInfo.Store(epInfo) 517 return epInfo 518 } 519 520 // removeSubConnFromEndpointMapEntry removes the scw from its map entry if 521 // present. 522 // 523 // Caller must hold b.mu. 524 func (b *outlierDetectionBalancer) removeSubConnFromEndpointMapEntry(scw *subConnWrapper) { 525 epInfo := scw.endpointInfo.Load() 526 if epInfo == nil { 527 return 528 } 529 for i, sw := range epInfo.sws { 530 if scw == sw { 531 epInfo.sws = append(epInfo.sws[:i], epInfo.sws[i+1:]...) 532 return 533 } 534 } 535 } 536 537 func (b *outlierDetectionBalancer) UpdateAddresses(sc balancer.SubConn, addrs []resolver.Address) { 538 scw, ok := sc.(*subConnWrapper) 539 if !ok { 540 // Return, shouldn't happen if passed up scw 541 return 542 } 543 544 b.ClientConn.UpdateAddresses(scw.SubConn, addrs) 545 b.mu.Lock() 546 defer b.mu.Unlock() 547 548 // Note that 0 addresses is a valid update/state for a SubConn to be in. 549 // This is correctly handled by this algorithm (handled as part of a non singular 550 // old address/new address). 551 switch { 552 case len(scw.addresses) == 1 && len(addrs) == 1: // single address to single address 553 // If the updated address is the same, then there is nothing to do 554 // past this point. 555 if scw.addresses[0].Addr == addrs[0].Addr { 556 return 557 } 558 b.removeSubConnFromEndpointMapEntry(scw) 559 endpointInfo := b.appendIfPresent(addrs[0].Addr, scw) 560 if endpointInfo == nil { // uneject unconditionally because could have come from an ejected endpoint 561 scw.uneject() 562 break 563 } 564 if endpointInfo.latestEjectionTimestamp.IsZero() { // relay new updated subconn state 565 scw.uneject() 566 } else { 567 scw.eject() 568 } 569 case len(scw.addresses) == 1: // single address to multiple/no addresses 570 b.removeSubConnFromEndpointMapEntry(scw) 571 addrInfo := scw.endpointInfo.Load() 572 if addrInfo != nil { 573 addrInfo.callCounter.clear() 574 } 575 scw.uneject() 576 case len(addrs) == 1: // multiple/no addresses to single address 577 endpointInfo := b.appendIfPresent(addrs[0].Addr, scw) 578 if endpointInfo != nil && !endpointInfo.latestEjectionTimestamp.IsZero() { 579 scw.eject() 580 } 581 } // otherwise multiple/no addresses to multiple/no addresses; ignore 582 583 scw.addresses = addrs 584 } 585 586 // handleSubConnUpdate stores the recent state and forward the update 587 // if the SubConn is not ejected. 588 func (b *outlierDetectionBalancer) handleSubConnUpdate(u *scUpdate) { 589 scw := u.scw 590 scw.clearHealthListener() 591 b.child.updateSubConnState(scw, u.state) 592 } 593 594 func (b *outlierDetectionBalancer) handleSubConnHealthUpdate(u *scHealthUpdate) { 595 b.child.updateSubConnHealthState(u.scw, u.state) 596 } 597 598 // handleEjectedUpdate handles any SubConns that get ejected/unejected, and 599 // forwards the appropriate corresponding subConnState to the child policy. 600 func (b *outlierDetectionBalancer) handleEjectedUpdate(u *ejectionUpdate) { 601 b.child.handleEjectionUpdate(u) 602 } 603 604 // handleChildStateUpdate forwards the picker update wrapped in a wrapped picker 605 // with the noop picker bit present. 606 func (b *outlierDetectionBalancer) handleChildStateUpdate(u balancer.State) { 607 b.childState = u 608 b.mu.Lock() 609 if b.inhibitPickerUpdates { 610 // If a child's state is updated during the suppression of child 611 // updates, the synchronous handleLBConfigUpdate function with respect 612 // to UpdateClientConnState should return a picker unconditionally. 613 b.updateUnconditionally = true 614 b.mu.Unlock() 615 return 616 } 617 noopCfg := b.noopConfig() 618 b.mu.Unlock() 619 b.recentPickerNoop = noopCfg 620 b.ClientConn.UpdateState(balancer.State{ 621 ConnectivityState: b.childState.ConnectivityState, 622 Picker: &wrappedPicker{ 623 childPicker: b.childState.Picker, 624 noopPicker: noopCfg, 625 }, 626 }) 627 } 628 629 // handleLBConfigUpdate compares whether the new config is a noop config or not, 630 // to the noop bit in the picker if present. It updates the picker if this bit 631 // changed compared to the picker currently in use. 632 func (b *outlierDetectionBalancer) handleLBConfigUpdate(u lbCfgUpdate) { 633 lbCfg := u.lbCfg 634 noopCfg := lbCfg.SuccessRateEjection == nil && lbCfg.FailurePercentageEjection == nil 635 // If the child has sent its first update and this config flips the noop 636 // bit compared to the most recent picker update sent upward, then a new 637 // picker with this updated bit needs to be forwarded upward. If a child 638 // update was received during the suppression of child updates within 639 // UpdateClientConnState(), then a new picker needs to be forwarded with 640 // this updated state, irregardless of whether this new configuration flips 641 // the bit. 642 if b.childState.Picker != nil && noopCfg != b.recentPickerNoop || b.updateUnconditionally { 643 b.recentPickerNoop = noopCfg 644 b.ClientConn.UpdateState(balancer.State{ 645 ConnectivityState: b.childState.ConnectivityState, 646 Picker: &wrappedPicker{ 647 childPicker: b.childState.Picker, 648 noopPicker: noopCfg, 649 }, 650 }) 651 } 652 b.inhibitPickerUpdates = false 653 b.updateUnconditionally = false 654 close(u.done) 655 } 656 657 func (b *outlierDetectionBalancer) run() { 658 defer b.done.Fire() 659 for { 660 select { 661 case update, ok := <-b.scUpdateCh.Get(): 662 if !ok { 663 return 664 } 665 b.scUpdateCh.Load() 666 if b.closed.HasFired() { // don't send SubConn updates to child after the balancer has been closed 667 return 668 } 669 switch u := update.(type) { 670 case *scUpdate: 671 b.handleSubConnUpdate(u) 672 case *ejectionUpdate: 673 b.handleEjectedUpdate(u) 674 case *scHealthUpdate: 675 b.handleSubConnHealthUpdate(u) 676 } 677 case update, ok := <-b.pickerUpdateCh.Get(): 678 if !ok { 679 return 680 } 681 b.pickerUpdateCh.Load() 682 if b.closed.HasFired() { // don't send picker updates to grpc after the balancer has been closed 683 return 684 } 685 switch u := update.(type) { 686 case balancer.State: 687 b.handleChildStateUpdate(u) 688 case lbCfgUpdate: 689 b.handleLBConfigUpdate(u) 690 } 691 case <-b.closed.Done(): 692 return 693 } 694 } 695 } 696 697 // intervalTimerAlgorithm ejects and unejects endpoints based on the Outlier 698 // Detection configuration and data about each endpoint from the previous 699 // interval. 700 func (b *outlierDetectionBalancer) intervalTimerAlgorithm() { 701 b.mu.Lock() 702 defer b.mu.Unlock() 703 b.timerStartTime = time.Now() 704 705 for _, epInfo := range b.endpoints.Values() { 706 epInfo.callCounter.swap() 707 } 708 709 if b.cfg.SuccessRateEjection != nil { 710 b.successRateAlgorithm() 711 } 712 713 if b.cfg.FailurePercentageEjection != nil { 714 b.failurePercentageAlgorithm() 715 } 716 717 for _, epInfo := range b.endpoints.Values() { 718 if epInfo.latestEjectionTimestamp.IsZero() && epInfo.ejectionTimeMultiplier > 0 { 719 epInfo.ejectionTimeMultiplier-- 720 continue 721 } 722 if epInfo.latestEjectionTimestamp.IsZero() { 723 // Endpoint is already not ejected, so no need to check for whether 724 // to uneject the endpoint below. 725 continue 726 } 727 et := time.Duration(b.cfg.BaseEjectionTime) * time.Duration(epInfo.ejectionTimeMultiplier) 728 met := max(time.Duration(b.cfg.BaseEjectionTime), time.Duration(b.cfg.MaxEjectionTime)) 729 uet := epInfo.latestEjectionTimestamp.Add(min(et, met)) 730 if now().After(uet) { 731 b.unejectEndpoint(epInfo) 732 } 733 } 734 735 // This conditional only for testing (since the interval timer algorithm is 736 // called manually), will never hit in production. 737 if b.intervalTimer != nil { 738 b.intervalTimer.Stop() 739 } 740 b.intervalTimer = afterFunc(time.Duration(b.cfg.Interval), b.intervalTimerAlgorithm) 741 } 742 743 // endpointsWithAtLeastRequestVolume returns a slice of endpoint information of 744 // all endpoints with at least request volume passed in. 745 // 746 // Caller must hold b.mu. 747 func (b *outlierDetectionBalancer) endpointsWithAtLeastRequestVolume(requestVolume uint32) []*endpointInfo { 748 var endpoints []*endpointInfo 749 for _, epInfo := range b.endpoints.Values() { 750 bucket1 := epInfo.callCounter.inactiveBucket 751 rv := bucket1.numSuccesses + bucket1.numFailures 752 if rv >= requestVolume { 753 endpoints = append(endpoints, epInfo) 754 } 755 } 756 return endpoints 757 } 758 759 // meanAndStdDev returns the mean and std dev of the fractions of successful 760 // requests of the endpoints passed in. 761 // 762 // Caller must hold b.mu. 763 func (b *outlierDetectionBalancer) meanAndStdDev(endpoints []*endpointInfo) (float64, float64) { 764 var totalFractionOfSuccessfulRequests float64 765 var mean float64 766 for _, epInfo := range endpoints { 767 bucket := epInfo.callCounter.inactiveBucket 768 rv := bucket.numSuccesses + bucket.numFailures 769 totalFractionOfSuccessfulRequests += float64(bucket.numSuccesses) / float64(rv) 770 } 771 mean = totalFractionOfSuccessfulRequests / float64(len(endpoints)) 772 var sumOfSquares float64 773 for _, epInfo := range endpoints { 774 bucket := epInfo.callCounter.inactiveBucket 775 rv := bucket.numSuccesses + bucket.numFailures 776 devFromMean := (float64(bucket.numSuccesses) / float64(rv)) - mean 777 sumOfSquares += devFromMean * devFromMean 778 } 779 variance := sumOfSquares / float64(len(endpoints)) 780 return mean, math.Sqrt(variance) 781 } 782 783 // successRateAlgorithm ejects any endpoints where the success rate falls below 784 // the other endpoints according to mean and standard deviation, and if overall 785 // applicable from other set heuristics. 786 // 787 // Caller must hold b.mu. 788 func (b *outlierDetectionBalancer) successRateAlgorithm() { 789 endpointsToConsider := b.endpointsWithAtLeastRequestVolume(b.cfg.SuccessRateEjection.RequestVolume) 790 if len(endpointsToConsider) < int(b.cfg.SuccessRateEjection.MinimumHosts) { 791 return 792 } 793 mean, stddev := b.meanAndStdDev(endpointsToConsider) 794 for _, epInfo := range endpointsToConsider { 795 bucket := epInfo.callCounter.inactiveBucket 796 ejectionCfg := b.cfg.SuccessRateEjection 797 if float64(b.numEndpointsEjected)/float64(b.endpoints.Len())*100 >= float64(b.cfg.MaxEjectionPercent) { 798 return 799 } 800 successRate := float64(bucket.numSuccesses) / float64(bucket.numSuccesses+bucket.numFailures) 801 requiredSuccessRate := mean - stddev*(float64(ejectionCfg.StdevFactor)/1000) 802 if successRate < requiredSuccessRate { 803 channelz.Infof(logger, b.channelzParent, "SuccessRate algorithm detected outlier: %s. Parameters: successRate=%f, mean=%f, stddev=%f, requiredSuccessRate=%f", epInfo, successRate, mean, stddev, requiredSuccessRate) 804 if uint32(rand.Int32N(100)) < ejectionCfg.EnforcementPercentage { 805 b.ejectEndpoint(epInfo) 806 } 807 } 808 } 809 } 810 811 // failurePercentageAlgorithm ejects any endpoints where the failure percentage 812 // rate exceeds a set enforcement percentage, if overall applicable from other 813 // set heuristics. 814 // 815 // Caller must hold b.mu. 816 func (b *outlierDetectionBalancer) failurePercentageAlgorithm() { 817 endpointsToConsider := b.endpointsWithAtLeastRequestVolume(b.cfg.FailurePercentageEjection.RequestVolume) 818 if len(endpointsToConsider) < int(b.cfg.FailurePercentageEjection.MinimumHosts) { 819 return 820 } 821 822 for _, epInfo := range endpointsToConsider { 823 bucket := epInfo.callCounter.inactiveBucket 824 ejectionCfg := b.cfg.FailurePercentageEjection 825 if float64(b.numEndpointsEjected)/float64(b.endpoints.Len())*100 >= float64(b.cfg.MaxEjectionPercent) { 826 return 827 } 828 failurePercentage := (float64(bucket.numFailures) / float64(bucket.numSuccesses+bucket.numFailures)) * 100 829 if failurePercentage > float64(b.cfg.FailurePercentageEjection.Threshold) { 830 channelz.Infof(logger, b.channelzParent, "FailurePercentage algorithm detected outlier: %s, failurePercentage=%f", epInfo, failurePercentage) 831 if uint32(rand.Int32N(100)) < ejectionCfg.EnforcementPercentage { 832 b.ejectEndpoint(epInfo) 833 } 834 } 835 } 836 } 837 838 // Caller must hold b.mu. 839 func (b *outlierDetectionBalancer) ejectEndpoint(epInfo *endpointInfo) { 840 b.numEndpointsEjected++ 841 epInfo.latestEjectionTimestamp = b.timerStartTime 842 epInfo.ejectionTimeMultiplier++ 843 for _, sbw := range epInfo.sws { 844 sbw.eject() 845 channelz.Infof(logger, b.channelzParent, "Subchannel ejected: %s", sbw) 846 } 847 848 } 849 850 // Caller must hold b.mu. 851 func (b *outlierDetectionBalancer) unejectEndpoint(epInfo *endpointInfo) { 852 b.numEndpointsEjected-- 853 epInfo.latestEjectionTimestamp = time.Time{} 854 for _, sbw := range epInfo.sws { 855 sbw.uneject() 856 channelz.Infof(logger, b.channelzParent, "Subchannel unejected: %s", sbw) 857 } 858 } 859 860 // synchronizingBalancerWrapper serializes calls into balancer (to uphold the 861 // balancer.Balancer API guarantee of synchronous calls). It also ensures a 862 // consistent order of locking mutexes when using SubConn listeners to avoid 863 // deadlocks. 864 type synchronizingBalancerWrapper struct { 865 // mu should not be used directly from outside this struct, instead use 866 // methods defined on the struct. 867 mu sync.Mutex 868 lb *gracefulswitch.Balancer 869 } 870 871 func (sbw *synchronizingBalancerWrapper) switchTo(builder balancer.Builder) error { 872 sbw.mu.Lock() 873 defer sbw.mu.Unlock() 874 return sbw.lb.SwitchTo(builder) 875 } 876 877 func (sbw *synchronizingBalancerWrapper) updateClientConnState(state balancer.ClientConnState) error { 878 sbw.mu.Lock() 879 defer sbw.mu.Unlock() 880 return sbw.lb.UpdateClientConnState(state) 881 } 882 883 func (sbw *synchronizingBalancerWrapper) resolverError(err error) { 884 sbw.mu.Lock() 885 defer sbw.mu.Unlock() 886 sbw.lb.ResolverError(err) 887 } 888 889 func (sbw *synchronizingBalancerWrapper) closeLB() { 890 sbw.mu.Lock() 891 defer sbw.mu.Unlock() 892 sbw.lb.Close() 893 } 894 895 func (sbw *synchronizingBalancerWrapper) exitIdle() { 896 sbw.mu.Lock() 897 defer sbw.mu.Unlock() 898 sbw.lb.ExitIdle() 899 } 900 901 func (sbw *synchronizingBalancerWrapper) updateSubConnHealthState(scw *subConnWrapper, scs balancer.SubConnState) { 902 sbw.mu.Lock() 903 defer sbw.mu.Unlock() 904 scw.updateSubConnHealthState(scs) 905 } 906 907 func (sbw *synchronizingBalancerWrapper) updateSubConnState(scw *subConnWrapper, scs balancer.SubConnState) { 908 sbw.mu.Lock() 909 defer sbw.mu.Unlock() 910 scw.updateSubConnConnectivityState(scs) 911 } 912 913 func (sbw *synchronizingBalancerWrapper) handleEjectionUpdate(u *ejectionUpdate) { 914 sbw.mu.Lock() 915 defer sbw.mu.Unlock() 916 if u.isEjected { 917 u.scw.handleEjection() 918 } else { 919 u.scw.handleUnejection() 920 } 921 } 922 923 // endpointInfo contains the runtime information about an endpoint that pertains 924 // to Outlier Detection. This struct and all of its fields is protected by 925 // outlierDetectionBalancer.mu in the case where it is accessed through the 926 // address or endpoint map. In the case of Picker callbacks, the writes to the 927 // activeBucket of callCounter are protected by atomically loading and storing 928 // unsafe.Pointers (see further explanation in incrementCounter()). 929 type endpointInfo struct { 930 // The call result counter object. 931 callCounter *callCounter 932 933 // The latest ejection timestamp, or zero if the endpoint is currently not 934 // ejected. 935 latestEjectionTimestamp time.Time 936 937 // The current ejection time multiplier, starting at 0. 938 ejectionTimeMultiplier int64 939 940 // A list of subchannel wrapper objects that correspond to this endpoint. 941 sws []*subConnWrapper 942 } 943 944 func (a *endpointInfo) String() string { 945 var res strings.Builder 946 res.WriteString("[") 947 for _, sw := range a.sws { 948 res.WriteString(sw.String()) 949 } 950 res.WriteString("]") 951 return res.String() 952 } 953 954 func newEndpointInfo() *endpointInfo { 955 return &endpointInfo{ 956 callCounter: newCallCounter(), 957 } 958 }