google.golang.org/grpc@v1.74.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 44 scUpdateCh *buffer.Unbounded 45 46 // The following fields are only referenced in the context of a work 47 // serializing buffer and don't need to be protected by a mutex. 48 49 // These two pieces of state will reach eventual consistency due to sync in 50 // run(), and child will always have the correctly updated SubConnState. 51 52 ejected bool 53 54 // addresses is the list of address(es) this SubConn was created with to 55 // help support any change in address(es) 56 addresses []resolver.Address 57 // latestHealthState is tracked to update the child policy during 58 // unejection. 59 latestHealthState balancer.SubConnState 60 61 // Access to the following fields are protected by a mutex. These fields 62 // should not be accessed from outside this file, instead use methods 63 // defined on the struct. 64 mu sync.Mutex 65 healthListener func(balancer.SubConnState) 66 // latestReceivedConnectivityState is the SubConn's most recent connectivity 67 // state received. It may not be delivered to the child balancer yet. It is 68 // used to ensure a health listener is registered with the SubConn only when 69 // the SubConn is READY. 70 latestReceivedConnectivityState connectivity.State 71 } 72 73 // eject causes the wrapper to report a state update with the TRANSIENT_FAILURE 74 // state, and to stop passing along updates from the underlying subchannel. 75 func (scw *subConnWrapper) eject() { 76 scw.scUpdateCh.Put(&ejectionUpdate{ 77 scw: scw, 78 isEjected: true, 79 }) 80 } 81 82 // uneject causes the wrapper to report a state update with the latest update 83 // from the underlying subchannel, and resume passing along updates from the 84 // underlying subchannel. 85 func (scw *subConnWrapper) uneject() { 86 scw.scUpdateCh.Put(&ejectionUpdate{ 87 scw: scw, 88 isEjected: false, 89 }) 90 } 91 92 func (scw *subConnWrapper) String() string { 93 return fmt.Sprintf("%+v", scw.addresses) 94 } 95 96 func (scw *subConnWrapper) RegisterHealthListener(listener func(balancer.SubConnState)) { 97 // gRPC currently supports two mechanisms that provide a health signal for 98 // a connection: client-side health checking and outlier detection. Earlier 99 // both these mechanisms signaled unhealthiness by setting the subchannel 100 // state to TRANSIENT_FAILURE. As part of the dualstack changes to make 101 // pick_first the universal leaf policy (see A61), both these mechanisms 102 // started using the new health listener to make health signal visible to 103 // the petiole policies without affecting the underlying connectivity 104 // management of the pick_first policy. 105 scw.mu.Lock() 106 defer scw.mu.Unlock() 107 108 if scw.latestReceivedConnectivityState != connectivity.Ready { 109 return 110 } 111 scw.healthListener = listener 112 if listener == nil { 113 scw.SubConn.RegisterHealthListener(nil) 114 return 115 } 116 117 scw.SubConn.RegisterHealthListener(func(scs balancer.SubConnState) { 118 scw.scUpdateCh.Put(&scHealthUpdate{ 119 scw: scw, 120 state: scs, 121 }) 122 }) 123 } 124 125 // updateSubConnHealthState stores the latest health state for unejection and 126 // sends updates the health listener. 127 func (scw *subConnWrapper) updateSubConnHealthState(scs balancer.SubConnState) { 128 scw.latestHealthState = scs 129 if scw.ejected { 130 return 131 } 132 scw.mu.Lock() 133 defer scw.mu.Unlock() 134 if scw.healthListener != nil { 135 scw.healthListener(scs) 136 } 137 } 138 139 // updateSubConnConnectivityState stores the latest connectivity state for 140 // unejection and updates the raw connectivity listener. 141 func (scw *subConnWrapper) updateSubConnConnectivityState(scs balancer.SubConnState) { 142 if scw.listener != nil { 143 scw.listener(scs) 144 } 145 } 146 147 func (scw *subConnWrapper) clearHealthListener() { 148 scw.mu.Lock() 149 defer scw.mu.Unlock() 150 scw.healthListener = nil 151 } 152 153 func (scw *subConnWrapper) handleUnejection() { 154 scw.ejected = false 155 // If scw.latestHealthState has never been written to will use the health 156 // state CONNECTING set during object creation. 157 scw.updateSubConnHealthState(scw.latestHealthState) 158 } 159 160 func (scw *subConnWrapper) handleEjection() { 161 scw.ejected = true 162 stateToUpdate := balancer.SubConnState{ 163 ConnectivityState: connectivity.TransientFailure, 164 } 165 scw.mu.Lock() 166 defer scw.mu.Unlock() 167 if scw.healthListener != nil { 168 scw.healthListener(stateToUpdate) 169 } 170 } 171 172 func (scw *subConnWrapper) setLatestConnectivityState(state connectivity.State) { 173 scw.mu.Lock() 174 defer scw.mu.Unlock() 175 scw.latestReceivedConnectivityState = state 176 }