google.golang.org/grpc@v1.72.2/balancer/rls/picker.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 rls 20 21 import ( 22 "errors" 23 "fmt" 24 "strings" 25 "sync/atomic" 26 "time" 27 28 "google.golang.org/grpc/balancer" 29 "google.golang.org/grpc/balancer/rls/internal/keys" 30 "google.golang.org/grpc/codes" 31 "google.golang.org/grpc/connectivity" 32 estats "google.golang.org/grpc/experimental/stats" 33 internalgrpclog "google.golang.org/grpc/internal/grpclog" 34 rlspb "google.golang.org/grpc/internal/proto/grpc_lookup_v1" 35 "google.golang.org/grpc/metadata" 36 "google.golang.org/grpc/status" 37 ) 38 39 var ( 40 errRLSThrottled = errors.New("RLS call throttled at client side") 41 42 // Function to compute data cache entry size. 43 computeDataCacheEntrySize = dcEntrySize 44 ) 45 46 // exitIdler wraps the only method on the BalancerGroup that the picker calls. 47 type exitIdler interface { 48 ExitIdleOne(id string) 49 } 50 51 // rlsPicker selects the subConn to be used for a particular RPC. It does not 52 // manage subConns directly and delegates to pickers provided by child policies. 53 type rlsPicker struct { 54 // The keyBuilder map used to generate RLS keys for the RPC. This is built 55 // by the LB policy based on the received ServiceConfig. 56 kbm keys.BuilderMap 57 // Endpoint from the user's original dial target. Used to set the `host_key` 58 // field in `extra_keys`. 59 origEndpoint string 60 61 lb *rlsBalancer 62 63 // The picker is given its own copy of the below fields from the RLS LB policy 64 // to avoid having to grab the mutex on the latter. 65 rlsServerTarget string 66 grpcTarget string 67 metricsRecorder estats.MetricsRecorder 68 defaultPolicy *childPolicyWrapper // Child policy for the default target. 69 ctrlCh *controlChannel // Control channel to the RLS server. 70 maxAge time.Duration // Cache max age from LB config. 71 staleAge time.Duration // Cache stale age from LB config. 72 bg exitIdler 73 logger *internalgrpclog.PrefixLogger 74 } 75 76 // isFullMethodNameValid return true if name is of the form `/service/method`. 77 func isFullMethodNameValid(name string) bool { 78 return strings.HasPrefix(name, "/") && strings.Count(name, "/") == 2 79 } 80 81 // Pick makes the routing decision for every outbound RPC. 82 func (p *rlsPicker) Pick(info balancer.PickInfo) (balancer.PickResult, error) { 83 if name := info.FullMethodName; !isFullMethodNameValid(name) { 84 return balancer.PickResult{}, fmt.Errorf("rls: method name %q is not of the form '/service/method", name) 85 } 86 87 // Build the request's keys using the key builders from LB config. 88 md, _ := metadata.FromOutgoingContext(info.Ctx) 89 reqKeys := p.kbm.RLSKey(md, p.origEndpoint, info.FullMethodName) 90 91 p.lb.cacheMu.Lock() 92 var pr balancer.PickResult 93 var err error 94 95 // Record metrics without the cache mutex held, to prevent lock contention 96 // between concurrent RPC's and their Pick calls. Metrics Recording can 97 // potentially be expensive. 98 metricsCallback := func() {} 99 defer func() { 100 p.lb.cacheMu.Unlock() 101 metricsCallback() 102 }() 103 104 // Lookup data cache and pending request map using request path and keys. 105 cacheKey := cacheKey{path: info.FullMethodName, keys: reqKeys.Str} 106 dcEntry := p.lb.dataCache.getEntry(cacheKey) 107 pendingEntry := p.lb.pendingMap[cacheKey] 108 now := time.Now() 109 110 switch { 111 // No data cache entry. No pending request. 112 case dcEntry == nil && pendingEntry == nil: 113 throttled := p.sendRouteLookupRequestLocked(cacheKey, &backoffState{bs: defaultBackoffStrategy}, reqKeys.Map, rlspb.RouteLookupRequest_REASON_MISS, "") 114 if throttled { 115 pr, metricsCallback, err = p.useDefaultPickIfPossible(info, errRLSThrottled) 116 return pr, err 117 } 118 return balancer.PickResult{}, balancer.ErrNoSubConnAvailable 119 120 // No data cache entry. Pending request exits. 121 case dcEntry == nil && pendingEntry != nil: 122 return balancer.PickResult{}, balancer.ErrNoSubConnAvailable 123 124 // Data cache hit. No pending request. 125 case dcEntry != nil && pendingEntry == nil: 126 if dcEntry.expiryTime.After(now) { 127 if !dcEntry.staleTime.IsZero() && dcEntry.staleTime.Before(now) && dcEntry.backoffTime.Before(now) { 128 p.sendRouteLookupRequestLocked(cacheKey, dcEntry.backoffState, reqKeys.Map, rlspb.RouteLookupRequest_REASON_STALE, dcEntry.headerData) 129 } 130 // Delegate to child policies. 131 pr, metricsCallback, err = p.delegateToChildPoliciesLocked(dcEntry, info) 132 return pr, err 133 } 134 135 // We get here only if the data cache entry has expired. If entry is in 136 // backoff, delegate to default target or fail the pick. 137 if dcEntry.backoffState != nil && dcEntry.backoffTime.After(now) { 138 // Avoid propagating the status code received on control plane RPCs to the 139 // data plane which can lead to unexpected outcomes as we do not control 140 // the status code sent by the control plane. Propagating the status 141 // message received from the control plane is still fine, as it could be 142 // useful for debugging purposes. 143 st := dcEntry.status 144 pr, metricsCallback, err = p.useDefaultPickIfPossible(info, status.Error(codes.Unavailable, fmt.Sprintf("most recent error from RLS server: %v", st.Error()))) 145 return pr, err 146 } 147 148 // We get here only if the entry has expired and is not in backoff. 149 throttled := p.sendRouteLookupRequestLocked(cacheKey, dcEntry.backoffState, reqKeys.Map, rlspb.RouteLookupRequest_REASON_MISS, "") 150 if throttled { 151 pr, metricsCallback, err = p.useDefaultPickIfPossible(info, errRLSThrottled) 152 return pr, err 153 } 154 return balancer.PickResult{}, balancer.ErrNoSubConnAvailable 155 156 // Data cache hit. Pending request exists. 157 default: 158 if dcEntry.expiryTime.After(now) { 159 pr, metricsCallback, err = p.delegateToChildPoliciesLocked(dcEntry, info) 160 return pr, err 161 } 162 // Data cache entry has expired and pending request exists. Queue pick. 163 return balancer.PickResult{}, balancer.ErrNoSubConnAvailable 164 } 165 } 166 167 // errToPickResult is a helper function which converts the error value returned 168 // by Pick() to a string that represents the pick result. 169 func errToPickResult(err error) string { 170 if err == nil { 171 return "complete" 172 } 173 if errors.Is(err, balancer.ErrNoSubConnAvailable) { 174 return "queue" 175 } 176 if _, ok := status.FromError(err); ok { 177 return "drop" 178 } 179 return "fail" 180 } 181 182 // delegateToChildPoliciesLocked is a helper function which iterates through the 183 // list of child policy wrappers in a cache entry and attempts to find a child 184 // policy to which this RPC can be routed to. If all child policies are in 185 // TRANSIENT_FAILURE, we delegate to the last child policy arbitrarily. Returns 186 // a function to be invoked to record metrics. 187 func (p *rlsPicker) delegateToChildPoliciesLocked(dcEntry *cacheEntry, info balancer.PickInfo) (balancer.PickResult, func(), error) { 188 const rlsDataHeaderName = "x-google-rls-data" 189 for i, cpw := range dcEntry.childPolicyWrappers { 190 state := (*balancer.State)(atomic.LoadPointer(&cpw.state)) 191 // Delegate to the child policy if it is not in TRANSIENT_FAILURE, or if 192 // it is the last one (which handles the case of delegating to the last 193 // child picker if all child policies are in TRANSIENT_FAILURE). 194 if state.ConnectivityState != connectivity.TransientFailure || i == len(dcEntry.childPolicyWrappers)-1 { 195 // Any header data received from the RLS server is stored in the 196 // cache entry and needs to be sent to the actual backend in the 197 // X-Google-RLS-Data header. 198 res, err := state.Picker.Pick(info) 199 if err != nil { 200 pr := errToPickResult(err) 201 return res, func() { 202 if pr == "queue" { 203 // Don't record metrics for queued Picks. 204 return 205 } 206 targetPicksMetric.Record(p.metricsRecorder, 1, p.grpcTarget, p.rlsServerTarget, cpw.target, pr) 207 }, err 208 } 209 210 if res.Metadata == nil { 211 res.Metadata = metadata.Pairs(rlsDataHeaderName, dcEntry.headerData) 212 } else { 213 res.Metadata.Append(rlsDataHeaderName, dcEntry.headerData) 214 } 215 return res, func() { 216 targetPicksMetric.Record(p.metricsRecorder, 1, p.grpcTarget, p.rlsServerTarget, cpw.target, "complete") 217 }, nil 218 } 219 } 220 221 // In the unlikely event that we have a cache entry with no targets, we end up 222 // queueing the RPC. 223 return balancer.PickResult{}, func() {}, balancer.ErrNoSubConnAvailable 224 } 225 226 // useDefaultPickIfPossible is a helper method which delegates to the default 227 // target if one is configured, or fails the pick with the given error. Returns 228 // a function to be invoked to record metrics. 229 func (p *rlsPicker) useDefaultPickIfPossible(info balancer.PickInfo, errOnNoDefault error) (balancer.PickResult, func(), error) { 230 if p.defaultPolicy != nil { 231 state := (*balancer.State)(atomic.LoadPointer(&p.defaultPolicy.state)) 232 res, err := state.Picker.Pick(info) 233 pr := errToPickResult(err) 234 return res, func() { 235 if pr == "queue" { 236 // Don't record metrics for queued Picks. 237 return 238 } 239 defaultTargetPicksMetric.Record(p.metricsRecorder, 1, p.grpcTarget, p.rlsServerTarget, p.defaultPolicy.target, pr) 240 }, err 241 } 242 243 return balancer.PickResult{}, func() { 244 failedPicksMetric.Record(p.metricsRecorder, 1, p.grpcTarget, p.rlsServerTarget) 245 }, errOnNoDefault 246 } 247 248 // sendRouteLookupRequestLocked adds an entry to the pending request map and 249 // sends out an RLS request using the passed in arguments. Returns a value 250 // indicating if the request was throttled by the client-side adaptive 251 // throttler. 252 func (p *rlsPicker) sendRouteLookupRequestLocked(cacheKey cacheKey, bs *backoffState, reqKeys map[string]string, reason rlspb.RouteLookupRequest_Reason, staleHeaders string) bool { 253 if p.lb.pendingMap[cacheKey] != nil { 254 return false 255 } 256 257 p.lb.pendingMap[cacheKey] = bs 258 throttled := p.ctrlCh.lookup(reqKeys, reason, staleHeaders, func(targets []string, headerData string, err error) { 259 p.handleRouteLookupResponse(cacheKey, targets, headerData, err) 260 }) 261 if throttled { 262 delete(p.lb.pendingMap, cacheKey) 263 } 264 return throttled 265 } 266 267 // handleRouteLookupResponse is the callback invoked by the control channel upon 268 // receipt of an RLS response. Modifies the data cache and pending requests map 269 // and sends a new picker. 270 // 271 // Acquires the write-lock on the cache. Caller must not hold p.lb.cacheMu. 272 func (p *rlsPicker) handleRouteLookupResponse(cacheKey cacheKey, targets []string, headerData string, err error) { 273 p.logger.Infof("Received RLS response for key %+v with targets %+v, headerData %q, err: %v", cacheKey, targets, headerData, err) 274 275 p.lb.cacheMu.Lock() 276 defer func() { 277 // Pending request map entry is unconditionally deleted since the request is 278 // no longer pending. 279 p.logger.Infof("Removing pending request entry for key %+v", cacheKey) 280 delete(p.lb.pendingMap, cacheKey) 281 p.lb.sendNewPicker() 282 p.lb.cacheMu.Unlock() 283 }() 284 285 // Lookup the data cache entry or create a new one. 286 dcEntry := p.lb.dataCache.getEntry(cacheKey) 287 if dcEntry == nil { 288 dcEntry = &cacheEntry{} 289 if _, ok := p.lb.dataCache.addEntry(cacheKey, dcEntry); !ok { 290 // This is a very unlikely case where we are unable to add a 291 // data cache entry. Log and leave. 292 p.logger.Warningf("Failed to add data cache entry for %+v", cacheKey) 293 return 294 } 295 } 296 297 // For failed requests, the data cache entry is modified as follows: 298 // - status is set to error returned from the control channel 299 // - current backoff state is available in the pending entry 300 // - `retries` field is incremented and 301 // - backoff state is moved to the data cache 302 // - backoffTime is set to the time indicated by the backoff state 303 // - backoffExpirationTime is set to twice the backoff time 304 // - backoffTimer is set to fire after backoffTime 305 // 306 // When a proactive cache refresh fails, this would leave the targets and the 307 // expiry time from the old entry unchanged. And this mean that the old valid 308 // entry would be used until expiration, and a new picker would be sent upon 309 // backoff expiry. 310 now := time.Now() 311 312 // "An RLS request is considered to have failed if it returns a non-OK 313 // status or the RLS response's targets list is non-empty." - RLS LB Policy 314 // design. 315 if len(targets) == 0 && err == nil { 316 err = fmt.Errorf("RLS response's target list does not contain any entries for key %+v", cacheKey) 317 // If err is set, rpc error from the control plane and no control plane 318 // configuration is why no targets were passed into this helper, no need 319 // to specify and tell the user this information. 320 } 321 if err != nil { 322 dcEntry.status = err 323 pendingEntry := p.lb.pendingMap[cacheKey] 324 pendingEntry.retries++ 325 backoffTime := pendingEntry.bs.Backoff(pendingEntry.retries) 326 dcEntry.backoffState = pendingEntry 327 dcEntry.backoffTime = now.Add(backoffTime) 328 dcEntry.backoffExpiryTime = now.Add(2 * backoffTime) 329 if dcEntry.backoffState.timer != nil { 330 dcEntry.backoffState.timer.Stop() 331 } 332 dcEntry.backoffState.timer = time.AfterFunc(backoffTime, p.lb.sendNewPicker) 333 return 334 } 335 336 // For successful requests, the cache entry is modified as follows: 337 // - childPolicyWrappers is set to point to the child policy wrappers 338 // associated with the targets specified in the received response 339 // - headerData is set to the value received in the response 340 // - expiryTime, stateTime and earliestEvictionTime are set 341 // - status is set to nil (OK status) 342 // - backoff state is cleared 343 p.setChildPolicyWrappersInCacheEntry(dcEntry, targets) 344 dcEntry.headerData = headerData 345 dcEntry.expiryTime = now.Add(p.maxAge) 346 if p.staleAge != 0 { 347 dcEntry.staleTime = now.Add(p.staleAge) 348 } 349 dcEntry.earliestEvictTime = now.Add(minEvictDuration) 350 dcEntry.status = nil 351 dcEntry.backoffState = &backoffState{bs: defaultBackoffStrategy} 352 dcEntry.backoffTime = time.Time{} 353 dcEntry.backoffExpiryTime = time.Time{} 354 p.lb.dataCache.updateEntrySize(dcEntry, computeDataCacheEntrySize(cacheKey, dcEntry)) 355 } 356 357 // setChildPolicyWrappersInCacheEntry sets up the childPolicyWrappers field in 358 // the cache entry to point to the child policy wrappers for the targets 359 // specified in the RLS response. 360 // 361 // Caller must hold a write-lock on p.lb.cacheMu. 362 func (p *rlsPicker) setChildPolicyWrappersInCacheEntry(dcEntry *cacheEntry, newTargets []string) { 363 // If the childPolicyWrappers field is already pointing to the right targets, 364 // then the field's value does not need to change. 365 targetsChanged := true 366 func() { 367 if cpws := dcEntry.childPolicyWrappers; cpws != nil { 368 if len(newTargets) != len(cpws) { 369 return 370 } 371 for i, target := range newTargets { 372 if cpws[i].target != target { 373 return 374 } 375 } 376 targetsChanged = false 377 } 378 }() 379 if !targetsChanged { 380 return 381 } 382 383 // If the childPolicyWrappers field is not already set to the right targets, 384 // then it must be reset. We construct a new list of child policies and 385 // then swap out the old list for the new one. 386 newChildPolicies := p.lb.acquireChildPolicyReferences(newTargets) 387 oldChildPolicyTargets := make([]string, len(dcEntry.childPolicyWrappers)) 388 for i, cpw := range dcEntry.childPolicyWrappers { 389 oldChildPolicyTargets[i] = cpw.target 390 } 391 p.lb.releaseChildPolicyReferences(oldChildPolicyTargets) 392 dcEntry.childPolicyWrappers = newChildPolicies 393 } 394 395 func dcEntrySize(key cacheKey, entry *cacheEntry) int64 { 396 return int64(len(key.path) + len(key.keys) + len(entry.headerData)) 397 }