google.golang.org/grpc@v1.72.2/xds/internal/balancer/outlierdetection/subconn_wrapper.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 package outlierdetection 19 20 import ( 21 "fmt" 22 "sync" 23 "sync/atomic" 24 25 "google.golang.org/grpc/balancer" 26 "google.golang.org/grpc/connectivity" 27 "google.golang.org/grpc/internal/buffer" 28 "google.golang.org/grpc/resolver" 29 ) 30 31 // subConnWrapper wraps every created SubConn in the Outlier Detection Balancer, 32 // to help track the latest state update from the underlying SubConn, and also 33 // whether or not this SubConn is ejected. 34 type subConnWrapper struct { 35 balancer.SubConn 36 // endpointInfo is a pointer to the subConnWrapper's corresponding endpoint 37 // map entry, if the map entry exists. It is accessed atomically. 38 endpointInfo atomic.Pointer[endpointInfo] 39 // The following fields are set during object creation and read-only after 40 // that. 41 42 listener func(balancer.SubConnState) 43 // healthListenerEnabled indicates whether the leaf LB policy is using a 44 // generic health listener. When enabled, ejection updates are sent via the 45 // health listener instead of the connectivity listener. Once Dualstack 46 // changes are complete, all SubConns will be created by pickfirst which 47 // uses the health listener. 48 // TODO: https://github.com/grpc/grpc-go/issues/7915 - Once Dualstack 49 // changes are complete, all SubConns will be created by pick_first and 50 // outlier detection will only use the health listener for ejection and 51 // this field can be removed. 52 healthListenerEnabled bool 53 54 scUpdateCh *buffer.Unbounded 55 56 // The following fields are only referenced in the context of a work 57 // serializing buffer and don't need to be protected by a mutex. 58 59 // These two pieces of state will reach eventual consistency due to sync in 60 // run(), and child will always have the correctly updated SubConnState. 61 62 ejected bool 63 64 // addresses is the list of address(es) this SubConn was created with to 65 // help support any change in address(es) 66 addresses []resolver.Address 67 // latestHealthState is tracked to update the child policy during 68 // unejection. 69 latestHealthState balancer.SubConnState 70 // latestRawConnectivityState is tracked to update the child policy during 71 // unejection. 72 latestRawConnectivityState balancer.SubConnState 73 74 // Access to the following fields are protected by a mutex. These fields 75 // should not be accessed from outside this file, instead use methods 76 // defined on the struct. 77 mu sync.Mutex 78 healthListener func(balancer.SubConnState) 79 // latestReceivedConnectivityState is the SubConn's most recent connectivity 80 // state received. It may not be delivered to the child balancer yet. It is 81 // used to ensure a health listener is registered with the SubConn only when 82 // the SubConn is READY. 83 latestReceivedConnectivityState connectivity.State 84 } 85 86 // eject causes the wrapper to report a state update with the TRANSIENT_FAILURE 87 // state, and to stop passing along updates from the underlying subchannel. 88 func (scw *subConnWrapper) eject() { 89 scw.scUpdateCh.Put(&ejectionUpdate{ 90 scw: scw, 91 isEjected: true, 92 }) 93 } 94 95 // uneject causes the wrapper to report a state update with the latest update 96 // from the underlying subchannel, and resume passing along updates from the 97 // underlying subchannel. 98 func (scw *subConnWrapper) uneject() { 99 scw.scUpdateCh.Put(&ejectionUpdate{ 100 scw: scw, 101 isEjected: false, 102 }) 103 } 104 105 func (scw *subConnWrapper) String() string { 106 return fmt.Sprintf("%+v", scw.addresses) 107 } 108 109 func (scw *subConnWrapper) RegisterHealthListener(listener func(balancer.SubConnState)) { 110 // gRPC currently supports two mechanisms that provide a health signal for 111 // a connection: client-side health checking and outlier detection. Earlier 112 // both these mechanisms signaled unhealthiness by setting the subchannel 113 // state to TRANSIENT_FAILURE. As part of the dualstack changes to make 114 // pick_first the universal leaf policy (see A61), both these mechanisms 115 // started using the new health listener to make health signal visible to 116 // the petiole policies without affecting the underlying connectivity 117 // management of the pick_first policy 118 if !scw.healthListenerEnabled { 119 logger.Errorf("Health listener unexpectedly registered on SubConn %v.", scw) 120 return 121 } 122 123 scw.mu.Lock() 124 defer scw.mu.Unlock() 125 126 if scw.latestReceivedConnectivityState != connectivity.Ready { 127 return 128 } 129 scw.healthListener = listener 130 if listener == nil { 131 scw.SubConn.RegisterHealthListener(nil) 132 return 133 } 134 135 scw.SubConn.RegisterHealthListener(func(scs balancer.SubConnState) { 136 scw.scUpdateCh.Put(&scHealthUpdate{ 137 scw: scw, 138 state: scs, 139 }) 140 }) 141 } 142 143 // updateSubConnHealthState stores the latest health state for unejection and 144 // sends updates the health listener. 145 func (scw *subConnWrapper) updateSubConnHealthState(scs balancer.SubConnState) { 146 scw.latestHealthState = scs 147 if scw.ejected { 148 return 149 } 150 scw.mu.Lock() 151 defer scw.mu.Unlock() 152 if scw.healthListener != nil { 153 scw.healthListener(scs) 154 } 155 } 156 157 // updateSubConnConnectivityState stores the latest connectivity state for 158 // unejection and updates the raw connectivity listener. 159 func (scw *subConnWrapper) updateSubConnConnectivityState(scs balancer.SubConnState) { 160 scw.latestRawConnectivityState = scs 161 // If the raw connectivity listener is used for ejection, and the SubConn is 162 // ejected, don't send the update. 163 if scw.ejected && !scw.healthListenerEnabled { 164 return 165 } 166 if scw.listener != nil { 167 scw.listener(scs) 168 } 169 } 170 171 func (scw *subConnWrapper) clearHealthListener() { 172 scw.mu.Lock() 173 defer scw.mu.Unlock() 174 scw.healthListener = nil 175 } 176 177 func (scw *subConnWrapper) handleUnejection() { 178 scw.ejected = false 179 if !scw.healthListenerEnabled { 180 // If scw.latestRawConnectivityState has never been written to will 181 // default to connectivity IDLE, which is fine. 182 scw.updateSubConnConnectivityState(scw.latestRawConnectivityState) 183 return 184 } 185 // If scw.latestHealthState has never been written to will use the health 186 // state CONNECTING set during object creation. 187 scw.updateSubConnHealthState(scw.latestHealthState) 188 } 189 190 func (scw *subConnWrapper) handleEjection() { 191 scw.ejected = true 192 stateToUpdate := balancer.SubConnState{ 193 ConnectivityState: connectivity.TransientFailure, 194 } 195 if !scw.healthListenerEnabled { 196 if scw.listener != nil { 197 scw.listener(stateToUpdate) 198 } 199 return 200 } 201 scw.mu.Lock() 202 defer scw.mu.Unlock() 203 if scw.healthListener != nil { 204 scw.healthListener(stateToUpdate) 205 } 206 } 207 208 func (scw *subConnWrapper) setLatestConnectivityState(state connectivity.State) { 209 scw.mu.Lock() 210 defer scw.mu.Unlock() 211 scw.latestReceivedConnectivityState = state 212 }