mirror of https://github.com/grpc/grpc-go.git
				
				
				
			rls: move the data cache implementation into the rls package (#5060)
This commit is contained in:
		
							parent
							
								
									ec7cf6c977
								
							
						
					
					
						commit
						b3d19efee6
					
				| 
						 | 
				
			
			@ -0,0 +1,404 @@
 | 
			
		|||
/*
 | 
			
		||||
 *
 | 
			
		||||
 * Copyright 2021 gRPC authors.
 | 
			
		||||
 *
 | 
			
		||||
 * Licensed under the Apache License, Version 2.0 (the "License");
 | 
			
		||||
 * you may not use this file except in compliance with the License.
 | 
			
		||||
 * You may obtain a copy of the License at
 | 
			
		||||
 *
 | 
			
		||||
 *     http://www.apache.org/licenses/LICENSE-2.0
 | 
			
		||||
 *
 | 
			
		||||
 * Unless required by applicable law or agreed to in writing, software
 | 
			
		||||
 * distributed under the License is distributed on an "AS IS" BASIS,
 | 
			
		||||
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
			
		||||
 * See the License for the specific language governing permissions and
 | 
			
		||||
 * limitations under the License.
 | 
			
		||||
 *
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
package rls
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"container/list"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"google.golang.org/grpc/internal/backoff"
 | 
			
		||||
	internalgrpclog "google.golang.org/grpc/internal/grpclog"
 | 
			
		||||
	"google.golang.org/grpc/internal/grpcsync"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// TODO(easwars): Remove this once all RLS code is merged.
 | 
			
		||||
//lint:file-ignore U1000 Ignore all unused code, not all code is merged yet.
 | 
			
		||||
 | 
			
		||||
// cacheKey represents the key used to uniquely identify an entry in the data
 | 
			
		||||
// cache and in the pending requests map.
 | 
			
		||||
type cacheKey struct {
 | 
			
		||||
	// path is the full path of the incoming RPC request.
 | 
			
		||||
	path string
 | 
			
		||||
	// keys is a stringified version of the RLS request key map built using the
 | 
			
		||||
	// RLS keyBuilder. Since maps are not a type which is comparable in Go, it
 | 
			
		||||
	// cannot be part of the key for another map (entries in the data cache and
 | 
			
		||||
	// pending requests map are stored in maps).
 | 
			
		||||
	keys string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// cacheEntry wraps all the data to be stored in a data cache entry.
 | 
			
		||||
type cacheEntry struct {
 | 
			
		||||
	// childPolicyWrappers contains the list of child policy wrappers
 | 
			
		||||
	// corresponding to the targets returned by the RLS server for this entry.
 | 
			
		||||
	childPolicyWrappers []*childPolicyWrapper
 | 
			
		||||
	// headerData is received in the RLS response and is to be sent in the
 | 
			
		||||
	// X-Google-RLS-Data header for matching RPCs.
 | 
			
		||||
	headerData string
 | 
			
		||||
	// expiryTime is the absolute time at which this cache entry entry stops
 | 
			
		||||
	// being valid. When an RLS request succeeds, this is set to the current
 | 
			
		||||
	// time plus the max_age field from the LB policy config.
 | 
			
		||||
	expiryTime time.Time
 | 
			
		||||
	// staleTime is the absolute time after which this cache entry will be
 | 
			
		||||
	// proactively refreshed if an incoming RPC matches this entry. When an RLS
 | 
			
		||||
	// request succeeds, this is set to the current time plus the stale_age from
 | 
			
		||||
	// the LB policy config.
 | 
			
		||||
	staleTime time.Time
 | 
			
		||||
	// earliestEvictTime is the absolute time before which this entry should not
 | 
			
		||||
	// be evicted from the cache. When a cache entry is created, this is set to
 | 
			
		||||
	// the current time plus a default value of 5 seconds. This is required to
 | 
			
		||||
	// make sure that a new entry added to the cache is not evicted before the
 | 
			
		||||
	// RLS response arrives (usually when the cache is too small).
 | 
			
		||||
	earliestEvictTime time.Time
 | 
			
		||||
 | 
			
		||||
	// status stores the RPC status of the previous RLS request for this
 | 
			
		||||
	// entry. Picks for entries with a non-nil value for this field are failed
 | 
			
		||||
	// with the error stored here.
 | 
			
		||||
	status error
 | 
			
		||||
	// backoffState contains all backoff related state. When an RLS request
 | 
			
		||||
	// succeeds, backoffState is reset. This state moves between the data cache
 | 
			
		||||
	// and the pending requests map.
 | 
			
		||||
	backoffState *backoffState
 | 
			
		||||
	// backoffTime is the absolute time at which the backoff period for this
 | 
			
		||||
	// entry ends. When an RLS request fails, this is set to the current time
 | 
			
		||||
	// plus the backoff value returned by the backoffState. The backoff timer is
 | 
			
		||||
	// also setup with this value. No new RLS requests are sent out for this
 | 
			
		||||
	// entry until the backoff period ends.
 | 
			
		||||
	//
 | 
			
		||||
	// Set to zero time instant upon a successful RLS response.
 | 
			
		||||
	backoffTime time.Time
 | 
			
		||||
	// backoffExpiryTime is the absolute time at which an entry which has gone
 | 
			
		||||
	// through backoff stops being valid.  When an RLS request fails, this is
 | 
			
		||||
	// set to the current time plus twice the backoff time. The cache expiry
 | 
			
		||||
	// timer will only delete entries for which both expiryTime and
 | 
			
		||||
	// backoffExpiryTime are in the past.
 | 
			
		||||
	//
 | 
			
		||||
	// Set to zero time instant upon a successful RLS response.
 | 
			
		||||
	backoffExpiryTime time.Time
 | 
			
		||||
 | 
			
		||||
	// size stores the size of this cache entry. Used to enforce the cache size
 | 
			
		||||
	// specified in the LB policy configuration.
 | 
			
		||||
	size int64
 | 
			
		||||
	// onEvict is the callback to be invoked when this cache entry is evicted.
 | 
			
		||||
	onEvict func()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// backoffState wraps all backoff related state associated with a cache entry.
 | 
			
		||||
type backoffState struct {
 | 
			
		||||
	// retries keeps track of the number of RLS failures, to be able to
 | 
			
		||||
	// determine the amount of time to backoff before the next attempt.
 | 
			
		||||
	retries int
 | 
			
		||||
	// bs is the exponential backoff implementation which returns the amount of
 | 
			
		||||
	// time to backoff, given the number of retries.
 | 
			
		||||
	bs backoff.Strategy
 | 
			
		||||
	// timer fires when the backoff period ends and incoming requests after this
 | 
			
		||||
	// will trigger a new RLS request.
 | 
			
		||||
	timer *time.Timer
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// lru is a cache implementation with a least recently used eviction policy.
 | 
			
		||||
// Internally it uses a doubly linked list, with the least recently used element
 | 
			
		||||
// at the front of the list and the most recently used element at the back of
 | 
			
		||||
// the list. The value stored in this cache will be of type `cacheKey`.
 | 
			
		||||
//
 | 
			
		||||
// It is not safe for concurrent access.
 | 
			
		||||
type lru struct {
 | 
			
		||||
	ll *list.List
 | 
			
		||||
 | 
			
		||||
	// A map from the value stored in the lru to its underlying list element is
 | 
			
		||||
	// maintained to have a clean API. Without this, a subset of the lru's API
 | 
			
		||||
	// would accept/return cacheKey while another subset would accept/return
 | 
			
		||||
	// list elements.
 | 
			
		||||
	m map[cacheKey]*list.Element
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// newLRU creates a new cache with a least recently used eviction policy.
 | 
			
		||||
func newLRU() *lru {
 | 
			
		||||
	return &lru{
 | 
			
		||||
		ll: list.New(),
 | 
			
		||||
		m:  make(map[cacheKey]*list.Element),
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (l *lru) addEntry(key cacheKey) {
 | 
			
		||||
	e := l.ll.PushBack(key)
 | 
			
		||||
	l.m[key] = e
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (l *lru) makeRecent(key cacheKey) {
 | 
			
		||||
	e := l.m[key]
 | 
			
		||||
	l.ll.MoveToBack(e)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (l *lru) removeEntry(key cacheKey) {
 | 
			
		||||
	e := l.m[key]
 | 
			
		||||
	l.ll.Remove(e)
 | 
			
		||||
	delete(l.m, key)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (l *lru) getLeastRecentlyUsed() cacheKey {
 | 
			
		||||
	e := l.ll.Front()
 | 
			
		||||
	if e == nil {
 | 
			
		||||
		return cacheKey{}
 | 
			
		||||
	}
 | 
			
		||||
	return e.Value.(cacheKey)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// iterateAndRun traverses the lru in least-recently-used order and calls the
 | 
			
		||||
// provided function for every element.
 | 
			
		||||
//
 | 
			
		||||
// Callers may delete the cache entry associated with the cacheKey passed into
 | 
			
		||||
// f, but they may not perform any other operation which reorders the elements
 | 
			
		||||
// in the lru.
 | 
			
		||||
func (l *lru) iterateAndRun(f func(cacheKey)) {
 | 
			
		||||
	var next *list.Element
 | 
			
		||||
	for e := l.ll.Front(); e != nil; e = next {
 | 
			
		||||
		next = e.Next()
 | 
			
		||||
		f(e.Value.(cacheKey))
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// dataCache contains a cache of RLS data used by the LB policy to make routing
 | 
			
		||||
// decisions.
 | 
			
		||||
//
 | 
			
		||||
// The dataCache will be keyed by the request's path and keys, represented by
 | 
			
		||||
// the `cacheKey` type. It will maintain the cache keys in an `lru` and the
 | 
			
		||||
// cache data, represented by the `cacheEntry` type, in a native map.
 | 
			
		||||
//
 | 
			
		||||
// It is not safe for concurrent access.
 | 
			
		||||
type dataCache struct {
 | 
			
		||||
	maxSize     int64 // Maximum allowed size.
 | 
			
		||||
	currentSize int64 // Current size.
 | 
			
		||||
	keys        *lru  // Cache keys maintained in lru order.
 | 
			
		||||
	entries     map[cacheKey]*cacheEntry
 | 
			
		||||
	logger      *internalgrpclog.PrefixLogger
 | 
			
		||||
	shutdown    *grpcsync.Event
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func newDataCache(size int64, logger *internalgrpclog.PrefixLogger) *dataCache {
 | 
			
		||||
	return &dataCache{
 | 
			
		||||
		maxSize:  size,
 | 
			
		||||
		keys:     newLRU(),
 | 
			
		||||
		entries:  make(map[cacheKey]*cacheEntry),
 | 
			
		||||
		logger:   logger,
 | 
			
		||||
		shutdown: grpcsync.NewEvent(),
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// resize changes the maximum allowed size of the data cache.
 | 
			
		||||
//
 | 
			
		||||
// The return value indicates if an entry with a valid backoff timer was
 | 
			
		||||
// evicted. This is important to the RLS LB policy which would send a new picker
 | 
			
		||||
// on the channel to re-process any RPCs queued as a result of this backoff
 | 
			
		||||
// timer.
 | 
			
		||||
func (dc *dataCache) resize(size int64) (backoffCancelled bool) {
 | 
			
		||||
	if dc.shutdown.HasFired() {
 | 
			
		||||
		return false
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	backoffCancelled = false
 | 
			
		||||
	for dc.currentSize > size {
 | 
			
		||||
		key := dc.keys.getLeastRecentlyUsed()
 | 
			
		||||
		entry, ok := dc.entries[key]
 | 
			
		||||
		if !ok {
 | 
			
		||||
			// This should never happen.
 | 
			
		||||
			dc.logger.Errorf("cacheKey %+v not found in the cache while attempting to resize it", key)
 | 
			
		||||
			break
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// When we encounter a cache entry whose minimum expiration time is in
 | 
			
		||||
		// the future, we abort the LRU pass, which may temporarily leave the
 | 
			
		||||
		// cache being too large. This is necessary to ensure that in cases
 | 
			
		||||
		// where the cache is too small, when we receive an RLS Response, we
 | 
			
		||||
		// keep the resulting cache entry around long enough for the pending
 | 
			
		||||
		// incoming requests to be re-processed through the new Picker. If we
 | 
			
		||||
		// didn't do this, then we'd risk throwing away each RLS response as we
 | 
			
		||||
		// receive it, in which case we would fail to actually route any of our
 | 
			
		||||
		// incoming requests.
 | 
			
		||||
		if entry.earliestEvictTime.After(time.Now()) {
 | 
			
		||||
			dc.logger.Warningf("cachekey %+v is too recent to be evicted. Stopping cache resizing for now", key)
 | 
			
		||||
			break
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// Stop the backoff timer before evicting the entry.
 | 
			
		||||
		if entry.backoffState != nil && entry.backoffState.timer != nil {
 | 
			
		||||
			if entry.backoffState.timer.Stop() {
 | 
			
		||||
				entry.backoffState.timer = nil
 | 
			
		||||
				backoffCancelled = true
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		dc.deleteAndcleanup(key, entry)
 | 
			
		||||
	}
 | 
			
		||||
	dc.maxSize = size
 | 
			
		||||
	return backoffCancelled
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// evictExpiredEntries sweeps through the cache and deletes expired entries. An
 | 
			
		||||
// expired entry is one for which both the `expiryTime` and `backoffExpiryTime`
 | 
			
		||||
// fields are in the past.
 | 
			
		||||
//
 | 
			
		||||
// The return value indicates if any expired entries were evicted.
 | 
			
		||||
//
 | 
			
		||||
// The LB policy invokes this method periodically to purge expired entries.
 | 
			
		||||
func (dc *dataCache) evictExpiredEntries() (evicted bool) {
 | 
			
		||||
	if dc.shutdown.HasFired() {
 | 
			
		||||
		return false
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	evicted = false
 | 
			
		||||
	dc.keys.iterateAndRun(func(key cacheKey) {
 | 
			
		||||
		entry, ok := dc.entries[key]
 | 
			
		||||
		if !ok {
 | 
			
		||||
			// This should never happen.
 | 
			
		||||
			dc.logger.Errorf("cacheKey %+v not found in the cache while attempting to perform periodic cleanup of expired entries", key)
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// Only evict entries for which both the data expiration time and
 | 
			
		||||
		// backoff expiration time fields are in the past.
 | 
			
		||||
		now := time.Now()
 | 
			
		||||
		if entry.expiryTime.After(now) || entry.backoffExpiryTime.After(now) {
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
		evicted = true
 | 
			
		||||
		dc.deleteAndcleanup(key, entry)
 | 
			
		||||
	})
 | 
			
		||||
	return evicted
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// resetBackoffState sweeps through the cache and for entries with a backoff
 | 
			
		||||
// state, the backoff timer is cancelled and the backoff state is reset. The
 | 
			
		||||
// return value indicates if any entries were mutated in this fashion.
 | 
			
		||||
//
 | 
			
		||||
// The LB policy invokes this method when the control channel moves from READY
 | 
			
		||||
// to TRANSIENT_FAILURE back to READY. See `monitorConnectivityState` method on
 | 
			
		||||
// the `controlChannel` type for more details.
 | 
			
		||||
func (dc *dataCache) resetBackoffState(newBackoffState *backoffState) (backoffReset bool) {
 | 
			
		||||
	if dc.shutdown.HasFired() {
 | 
			
		||||
		return false
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	backoffReset = false
 | 
			
		||||
	dc.keys.iterateAndRun(func(key cacheKey) {
 | 
			
		||||
		entry, ok := dc.entries[key]
 | 
			
		||||
		if !ok {
 | 
			
		||||
			// This should never happen.
 | 
			
		||||
			dc.logger.Errorf("cacheKey %+v not found in the cache while attempting to perform periodic cleanup of expired entries", key)
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if entry.backoffState == nil {
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
		if entry.backoffState.timer != nil {
 | 
			
		||||
			entry.backoffState.timer.Stop()
 | 
			
		||||
			entry.backoffState.timer = nil
 | 
			
		||||
		}
 | 
			
		||||
		entry.backoffState = &backoffState{bs: newBackoffState.bs}
 | 
			
		||||
		entry.backoffTime = time.Time{}
 | 
			
		||||
		entry.backoffExpiryTime = time.Time{}
 | 
			
		||||
		backoffReset = true
 | 
			
		||||
	})
 | 
			
		||||
	return backoffReset
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// addEntry adds a cache entry for the given key.
 | 
			
		||||
//
 | 
			
		||||
// Return value backoffCancelled indicates if a cache entry with a valid backoff
 | 
			
		||||
// timer was evicted to make space for the current entry. This is important to
 | 
			
		||||
// the RLS LB policy which would send a new picker on the channel to re-process
 | 
			
		||||
// any RPCs queued as a result of this backoff timer.
 | 
			
		||||
//
 | 
			
		||||
// Return value ok indicates if entry was successfully added to the cache.
 | 
			
		||||
func (dc *dataCache) addEntry(key cacheKey, entry *cacheEntry) (backoffCancelled bool, ok bool) {
 | 
			
		||||
	if dc.shutdown.HasFired() {
 | 
			
		||||
		return false, false
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Handle the extremely unlikely case that a single entry is bigger than the
 | 
			
		||||
	// size of the cache.
 | 
			
		||||
	if entry.size > dc.maxSize {
 | 
			
		||||
		return false, false
 | 
			
		||||
	}
 | 
			
		||||
	dc.entries[key] = entry
 | 
			
		||||
	dc.currentSize += entry.size
 | 
			
		||||
	dc.keys.addEntry(key)
 | 
			
		||||
	// If the new entry makes the cache go over its configured size, remove some
 | 
			
		||||
	// old entries.
 | 
			
		||||
	if dc.currentSize > dc.maxSize {
 | 
			
		||||
		backoffCancelled = dc.resize(dc.maxSize)
 | 
			
		||||
	}
 | 
			
		||||
	return backoffCancelled, true
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// updateEntrySize updates the size of a cache entry and the current size of the
 | 
			
		||||
// data cache. An entry's size can change upon receipt of an RLS response.
 | 
			
		||||
func (dc *dataCache) updateEntrySize(entry *cacheEntry, newSize int64) {
 | 
			
		||||
	dc.currentSize -= entry.size
 | 
			
		||||
	entry.size = newSize
 | 
			
		||||
	dc.currentSize += entry.size
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (dc *dataCache) getEntry(key cacheKey) *cacheEntry {
 | 
			
		||||
	if dc.shutdown.HasFired() {
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	entry, ok := dc.entries[key]
 | 
			
		||||
	if !ok {
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
	dc.keys.makeRecent(key)
 | 
			
		||||
	return entry
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (dc *dataCache) removeEntryForTesting(key cacheKey) {
 | 
			
		||||
	entry, ok := dc.entries[key]
 | 
			
		||||
	if !ok {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	dc.deleteAndcleanup(key, entry)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// deleteAndCleanup performs actions required at the time of deleting an entry
 | 
			
		||||
// from the data cache.
 | 
			
		||||
// - the entry is removed from the map of entries
 | 
			
		||||
// - current size of the data cache is update
 | 
			
		||||
// - the key is removed from the LRU
 | 
			
		||||
// - onEvict is invoked in a separate goroutine
 | 
			
		||||
func (dc *dataCache) deleteAndcleanup(key cacheKey, entry *cacheEntry) {
 | 
			
		||||
	delete(dc.entries, key)
 | 
			
		||||
	dc.currentSize -= entry.size
 | 
			
		||||
	dc.keys.removeEntry(key)
 | 
			
		||||
	if entry.onEvict != nil {
 | 
			
		||||
		go entry.onEvict()
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (dc *dataCache) stop() {
 | 
			
		||||
	dc.keys.iterateAndRun(func(key cacheKey) {
 | 
			
		||||
		entry, ok := dc.entries[key]
 | 
			
		||||
		if !ok {
 | 
			
		||||
			// This should never happen.
 | 
			
		||||
			dc.logger.Errorf("cacheKey %+v not found in the cache while shutting down", key)
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
		dc.deleteAndcleanup(key, entry)
 | 
			
		||||
	})
 | 
			
		||||
	dc.shutdown.Fire()
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1,244 +0,0 @@
 | 
			
		|||
/*
 | 
			
		||||
 *
 | 
			
		||||
 * Copyright 2020 gRPC authors.
 | 
			
		||||
 *
 | 
			
		||||
 * Licensed under the Apache License, Version 2.0 (the "License");
 | 
			
		||||
 * you may not use this file except in compliance with the License.
 | 
			
		||||
 * You may obtain a copy of the License at
 | 
			
		||||
 *
 | 
			
		||||
 *     http://www.apache.org/licenses/LICENSE-2.0
 | 
			
		||||
 *
 | 
			
		||||
 * Unless required by applicable law or agreed to in writing, software
 | 
			
		||||
 * distributed under the License is distributed on an "AS IS" BASIS,
 | 
			
		||||
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
			
		||||
 * See the License for the specific language governing permissions and
 | 
			
		||||
 * limitations under the License.
 | 
			
		||||
 *
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
// Package cache provides an LRU cache implementation to be used by the RLS LB
 | 
			
		||||
// policy to cache RLS response data.
 | 
			
		||||
package cache
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"container/list"
 | 
			
		||||
	"sync"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"google.golang.org/grpc/balancer"
 | 
			
		||||
	"google.golang.org/grpc/grpclog"
 | 
			
		||||
	"google.golang.org/grpc/internal/backoff"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
var logger = grpclog.Component("rls")
 | 
			
		||||
 | 
			
		||||
// Key represents the cache key used to uniquely identify a cache entry.
 | 
			
		||||
type Key struct {
 | 
			
		||||
	// Path is the full path of the incoming RPC request.
 | 
			
		||||
	Path string
 | 
			
		||||
	// KeyMap is a stringified version of the RLS request keys built using the
 | 
			
		||||
	// RLS keyBuilder. Since map is not a Type which is comparable in Go, it
 | 
			
		||||
	// cannot be part of the key for another map (the LRU cache is implemented
 | 
			
		||||
	// using a native map type).
 | 
			
		||||
	KeyMap string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Entry wraps all the data to be stored in a cache entry.
 | 
			
		||||
type Entry struct {
 | 
			
		||||
	// Mu synchronizes access to this particular cache entry. The LB policy
 | 
			
		||||
	// will also hold another mutex to synchronize access to the cache as a
 | 
			
		||||
	// whole. To avoid holding the top-level mutex for the whole duration for
 | 
			
		||||
	// which one particular cache entry is acted upon, we use this entry mutex.
 | 
			
		||||
	Mu sync.Mutex
 | 
			
		||||
	// ExpiryTime is the absolute time at which the data cached as part of this
 | 
			
		||||
	// entry stops being valid. When an RLS request succeeds, this is set to
 | 
			
		||||
	// the current time plus the max_age field from the LB policy config. An
 | 
			
		||||
	// entry with this field in the past is not used to process picks.
 | 
			
		||||
	ExpiryTime time.Time
 | 
			
		||||
	// BackoffExpiryTime is the absolute time at which an entry which has gone
 | 
			
		||||
	// through backoff stops being valid.  When an RLS request fails, this is
 | 
			
		||||
	// set to the current time plus twice the backoff time. The cache expiry
 | 
			
		||||
	// timer will only delete entries for which both ExpiryTime and
 | 
			
		||||
	// BackoffExpiryTime are in the past.
 | 
			
		||||
	BackoffExpiryTime time.Time
 | 
			
		||||
	// StaleTime is the absolute time after which this entry will be
 | 
			
		||||
	// proactively refreshed if we receive a request for it. When an RLS
 | 
			
		||||
	// request succeeds, this is set to the current time plus the stale_age
 | 
			
		||||
	// from the LB policy config.
 | 
			
		||||
	StaleTime time.Time
 | 
			
		||||
	// BackoffTime is the absolute time at which the backoff period for this
 | 
			
		||||
	// entry ends. The backoff timer is setup with this value. No new RLS
 | 
			
		||||
	// requests are sent out for this entry until the backoff period ends.
 | 
			
		||||
	BackoffTime time.Time
 | 
			
		||||
	// EarliestEvictTime is the absolute time before which this entry should
 | 
			
		||||
	// not be evicted from the cache. This is set to a default value of 5
 | 
			
		||||
	// seconds when the entry is created. This is required to make sure that a
 | 
			
		||||
	// new entry added to the cache is not evicted before the RLS response
 | 
			
		||||
	// arrives (usually when the cache is too small).
 | 
			
		||||
	EarliestEvictTime time.Time
 | 
			
		||||
	// CallStatus stores the RPC status of the previous RLS request for this
 | 
			
		||||
	// entry. Picks for entries with a non-nil value for this field are failed
 | 
			
		||||
	// with the error stored here.
 | 
			
		||||
	CallStatus error
 | 
			
		||||
	// Backoff contains all backoff related state. When an RLS request
 | 
			
		||||
	// succeeds, backoff state is reset.
 | 
			
		||||
	Backoff BackoffState
 | 
			
		||||
	// HeaderData is received in an RLS response and is to be sent in the
 | 
			
		||||
	// X-Google-RLS-Data header for matching RPCs.
 | 
			
		||||
	HeaderData string
 | 
			
		||||
	// ChildPicker is a very thin wrapper around the child policy wrapper.
 | 
			
		||||
	// The type is declared as a Picker interface since the users of
 | 
			
		||||
	// the cache only care about the picker provided by the child policy, and
 | 
			
		||||
	// this makes it easy for testing.
 | 
			
		||||
	ChildPicker balancer.Picker
 | 
			
		||||
 | 
			
		||||
	// size stores the size of this cache entry. Uses only a subset of the
 | 
			
		||||
	// fields. See `entrySize` for this is computed.
 | 
			
		||||
	size int64
 | 
			
		||||
	// key contains the cache key corresponding to this entry. This is required
 | 
			
		||||
	// from methods like `removeElement` which only have a pointer to the
 | 
			
		||||
	// list.Element which contains a reference to the cache.Entry. But these
 | 
			
		||||
	// methods need the cache.Key to be able to remove the entry from the
 | 
			
		||||
	// underlying map.
 | 
			
		||||
	key Key
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// BackoffState wraps all backoff related state associated with a cache entry.
 | 
			
		||||
type BackoffState struct {
 | 
			
		||||
	// Retries keeps track of the number of RLS failures, to be able to
 | 
			
		||||
	// determine the amount of time to backoff before the next attempt.
 | 
			
		||||
	Retries int
 | 
			
		||||
	// Backoff is an exponential backoff implementation which returns the
 | 
			
		||||
	// amount of time to backoff, given the number of retries.
 | 
			
		||||
	Backoff backoff.Strategy
 | 
			
		||||
	// Timer fires when the backoff period ends and incoming requests after
 | 
			
		||||
	// this will trigger a new RLS request.
 | 
			
		||||
	Timer *time.Timer
 | 
			
		||||
	// Callback provided by the LB policy to be notified when the backoff timer
 | 
			
		||||
	// expires. This will trigger a new picker to be returned to the
 | 
			
		||||
	// ClientConn, to force queued up RPCs to be retried.
 | 
			
		||||
	Callback func()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// LRU is a cache with a least recently used eviction policy. It is not safe
 | 
			
		||||
// for concurrent access.
 | 
			
		||||
type LRU struct {
 | 
			
		||||
	maxSize   int64
 | 
			
		||||
	usedSize  int64
 | 
			
		||||
	onEvicted func(Key, *Entry)
 | 
			
		||||
 | 
			
		||||
	ll    *list.List
 | 
			
		||||
	cache map[Key]*list.Element
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// NewLRU creates a cache.LRU with a size limit of maxSize and the provided
 | 
			
		||||
// eviction callback.
 | 
			
		||||
//
 | 
			
		||||
// Currently, only the cache.Key and the HeaderData field from cache.Entry
 | 
			
		||||
// count towards the size of the cache (other overhead per cache entry is not
 | 
			
		||||
// counted). The cache could temporarily exceed the configured maxSize because
 | 
			
		||||
// we want the entries to spend a configured minimum amount of time in the
 | 
			
		||||
// cache before they are LRU evicted (so that all the work performed in sending
 | 
			
		||||
// an RLS request and caching the response is not a total waste).
 | 
			
		||||
//
 | 
			
		||||
// The provided onEvited callback must not attempt to re-add the entry inline
 | 
			
		||||
// and the RLS LB policy does not have a need to do that.
 | 
			
		||||
//
 | 
			
		||||
// The cache package trusts the RLS policy (its only user) to supply a default
 | 
			
		||||
// minimum non-zero maxSize, in the event that the ServiceConfig does not
 | 
			
		||||
// provide a value for it.
 | 
			
		||||
func NewLRU(maxSize int64, onEvicted func(Key, *Entry)) *LRU {
 | 
			
		||||
	return &LRU{
 | 
			
		||||
		maxSize:   maxSize,
 | 
			
		||||
		onEvicted: onEvicted,
 | 
			
		||||
		ll:        list.New(),
 | 
			
		||||
		cache:     make(map[Key]*list.Element),
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Resize sets the size limit of the LRU to newMaxSize and removes older
 | 
			
		||||
// entries, if required, to comply with the new limit.
 | 
			
		||||
func (lru *LRU) Resize(newMaxSize int64) {
 | 
			
		||||
	lru.maxSize = newMaxSize
 | 
			
		||||
	lru.removeToFit(0)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// TODO(easwars): If required, make this function more sophisticated.
 | 
			
		||||
func entrySize(key Key, value *Entry) int64 {
 | 
			
		||||
	return int64(len(key.Path) + len(key.KeyMap) + len(value.HeaderData))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// removeToFit removes older entries from the cache to make room for a new
 | 
			
		||||
// entry of size newSize.
 | 
			
		||||
func (lru *LRU) removeToFit(newSize int64) {
 | 
			
		||||
	now := time.Now()
 | 
			
		||||
	for lru.usedSize+newSize > lru.maxSize {
 | 
			
		||||
		elem := lru.ll.Back()
 | 
			
		||||
		if elem == nil {
 | 
			
		||||
			// This is a corner case where the cache is empty, but the new entry
 | 
			
		||||
			// to be added is bigger than maxSize.
 | 
			
		||||
			logger.Info("rls: newly added cache entry exceeds cache maxSize")
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		entry := elem.Value.(*Entry)
 | 
			
		||||
		if t := entry.EarliestEvictTime; !t.IsZero() && t.Before(now) {
 | 
			
		||||
			// When the oldest entry is too new (it hasn't even spent a default
 | 
			
		||||
			// minimum amount of time in the cache), we abort and allow the
 | 
			
		||||
			// cache to grow bigger than the configured maxSize.
 | 
			
		||||
			logger.Info("rls: LRU eviction finds oldest entry to be too new. Allowing cache to exceed maxSize momentarily")
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
		lru.removeElement(elem)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Add adds a new entry to the cache.
 | 
			
		||||
func (lru *LRU) Add(key Key, value *Entry) {
 | 
			
		||||
	size := entrySize(key, value)
 | 
			
		||||
	elem, ok := lru.cache[key]
 | 
			
		||||
	if !ok {
 | 
			
		||||
		lru.removeToFit(size)
 | 
			
		||||
		lru.usedSize += size
 | 
			
		||||
		value.size = size
 | 
			
		||||
		value.key = key
 | 
			
		||||
		elem := lru.ll.PushFront(value)
 | 
			
		||||
		lru.cache[key] = elem
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	existing := elem.Value.(*Entry)
 | 
			
		||||
	sizeDiff := size - existing.size
 | 
			
		||||
	lru.removeToFit(sizeDiff)
 | 
			
		||||
	value.size = size
 | 
			
		||||
	elem.Value = value
 | 
			
		||||
	lru.ll.MoveToFront(elem)
 | 
			
		||||
	lru.usedSize += sizeDiff
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Remove removes a cache entry wth key key, if one exists.
 | 
			
		||||
func (lru *LRU) Remove(key Key) {
 | 
			
		||||
	if elem, ok := lru.cache[key]; ok {
 | 
			
		||||
		lru.removeElement(elem)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (lru *LRU) removeElement(e *list.Element) {
 | 
			
		||||
	entry := e.Value.(*Entry)
 | 
			
		||||
	lru.ll.Remove(e)
 | 
			
		||||
	delete(lru.cache, entry.key)
 | 
			
		||||
	lru.usedSize -= entry.size
 | 
			
		||||
	if lru.onEvicted != nil {
 | 
			
		||||
		lru.onEvicted(entry.key, entry)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Get returns a cache entry with key key.
 | 
			
		||||
func (lru *LRU) Get(key Key) *Entry {
 | 
			
		||||
	elem, ok := lru.cache[key]
 | 
			
		||||
	if !ok {
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
	lru.ll.MoveToFront(elem)
 | 
			
		||||
	return elem.Value.(*Entry)
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1,262 +0,0 @@
 | 
			
		|||
/*
 | 
			
		||||
 *
 | 
			
		||||
 * Copyright 2020 gRPC authors.
 | 
			
		||||
 *
 | 
			
		||||
 * Licensed under the Apache License, Version 2.0 (the "License");
 | 
			
		||||
 * you may not use this file except in compliance with the License.
 | 
			
		||||
 * You may obtain a copy of the License at
 | 
			
		||||
 *
 | 
			
		||||
 *     http://www.apache.org/licenses/LICENSE-2.0
 | 
			
		||||
 *
 | 
			
		||||
 * Unless required by applicable law or agreed to in writing, software
 | 
			
		||||
 * distributed under the License is distributed on an "AS IS" BASIS,
 | 
			
		||||
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
			
		||||
 * See the License for the specific language governing permissions and
 | 
			
		||||
 * limitations under the License.
 | 
			
		||||
 *
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
package cache
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"sync"
 | 
			
		||||
	"testing"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"github.com/google/go-cmp/cmp"
 | 
			
		||||
	"github.com/google/go-cmp/cmp/cmpopts"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
const (
 | 
			
		||||
	defaultTestCacheSize    = 5
 | 
			
		||||
	defaultTestCacheMaxSize = 1000000
 | 
			
		||||
	defaultTestTimeout      = 1 * time.Second
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// TestGet verifies the Add and Get methods of cache.LRU.
 | 
			
		||||
func TestGet(t *testing.T) {
 | 
			
		||||
	key1 := Key{Path: "/service1/method1", KeyMap: "k1=v1,k2=v2"}
 | 
			
		||||
	key2 := Key{Path: "/service2/method2", KeyMap: "k1=v1,k2=v2"}
 | 
			
		||||
	val1 := Entry{HeaderData: "h1=v1"}
 | 
			
		||||
	val2 := Entry{HeaderData: "h2=v2"}
 | 
			
		||||
 | 
			
		||||
	tests := []struct {
 | 
			
		||||
		desc      string
 | 
			
		||||
		keysToAdd []Key
 | 
			
		||||
		valsToAdd []*Entry
 | 
			
		||||
		keyToGet  Key
 | 
			
		||||
		wantEntry *Entry
 | 
			
		||||
	}{
 | 
			
		||||
		{
 | 
			
		||||
			desc:     "Empty cache",
 | 
			
		||||
			keyToGet: Key{},
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			desc:      "Single entry miss",
 | 
			
		||||
			keysToAdd: []Key{key1},
 | 
			
		||||
			valsToAdd: []*Entry{&val1},
 | 
			
		||||
			keyToGet:  Key{},
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			desc:      "Single entry hit",
 | 
			
		||||
			keysToAdd: []Key{key1},
 | 
			
		||||
			valsToAdd: []*Entry{&val1},
 | 
			
		||||
			keyToGet:  key1,
 | 
			
		||||
			wantEntry: &val1,
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			desc:      "Multi entry miss",
 | 
			
		||||
			keysToAdd: []Key{key1, key2},
 | 
			
		||||
			valsToAdd: []*Entry{&val1, &val2},
 | 
			
		||||
			keyToGet:  Key{},
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			desc:      "Multi entry hit",
 | 
			
		||||
			keysToAdd: []Key{key1, key2},
 | 
			
		||||
			valsToAdd: []*Entry{&val1, &val2},
 | 
			
		||||
			keyToGet:  key1,
 | 
			
		||||
			wantEntry: &val1,
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for _, test := range tests {
 | 
			
		||||
		t.Run(test.desc, func(t *testing.T) {
 | 
			
		||||
			lru := NewLRU(defaultTestCacheMaxSize, nil)
 | 
			
		||||
			for i, key := range test.keysToAdd {
 | 
			
		||||
				lru.Add(key, test.valsToAdd[i])
 | 
			
		||||
			}
 | 
			
		||||
			opts := []cmp.Option{
 | 
			
		||||
				cmpopts.IgnoreInterfaces(struct{ sync.Locker }{}),
 | 
			
		||||
				cmpopts.IgnoreUnexported(Entry{}),
 | 
			
		||||
			}
 | 
			
		||||
			if gotEntry := lru.Get(test.keyToGet); !cmp.Equal(gotEntry, test.wantEntry, opts...) {
 | 
			
		||||
				t.Errorf("lru.Get(%+v) = %+v, want %+v", test.keyToGet, gotEntry, test.wantEntry)
 | 
			
		||||
			}
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// TestRemove verifies the Add and Remove methods of cache.LRU.
 | 
			
		||||
func TestRemove(t *testing.T) {
 | 
			
		||||
	keys := []Key{
 | 
			
		||||
		{Path: "/service1/method1", KeyMap: "k1=v1,k2=v2"},
 | 
			
		||||
		{Path: "/service2/method2", KeyMap: "k1=v1,k2=v2"},
 | 
			
		||||
		{Path: "/service3/method3", KeyMap: "k1=v1,k2=v2"},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	lru := NewLRU(defaultTestCacheMaxSize, nil)
 | 
			
		||||
	for _, k := range keys {
 | 
			
		||||
		lru.Add(k, &Entry{})
 | 
			
		||||
	}
 | 
			
		||||
	for _, k := range keys {
 | 
			
		||||
		lru.Remove(k)
 | 
			
		||||
		if entry := lru.Get(k); entry != nil {
 | 
			
		||||
			t.Fatalf("lru.Get(%+v) after a call to lru.Remove succeeds, should have failed", k)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// TestExceedingSizeCausesEviction verifies the case where adding a new entry
 | 
			
		||||
// to the cache leads to eviction of old entries to make space for the new one.
 | 
			
		||||
func TestExceedingSizeCausesEviction(t *testing.T) {
 | 
			
		||||
	evictCh := make(chan Key, defaultTestCacheSize)
 | 
			
		||||
	onEvicted := func(k Key, _ *Entry) {
 | 
			
		||||
		t.Logf("evicted key {%+v} from cache", k)
 | 
			
		||||
		evictCh <- k
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	keysToFill := []Key{{Path: "a"}, {Path: "b"}, {Path: "c"}, {Path: "d"}, {Path: "e"}}
 | 
			
		||||
	keysCausingEviction := []Key{{Path: "f"}, {Path: "g"}, {Path: "h"}, {Path: "i"}, {Path: "j"}}
 | 
			
		||||
 | 
			
		||||
	lru := NewLRU(defaultTestCacheSize, onEvicted)
 | 
			
		||||
	for _, key := range keysToFill {
 | 
			
		||||
		lru.Add(key, &Entry{})
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for i, key := range keysCausingEviction {
 | 
			
		||||
		lru.Add(key, &Entry{})
 | 
			
		||||
 | 
			
		||||
		timer := time.NewTimer(defaultTestTimeout)
 | 
			
		||||
		select {
 | 
			
		||||
		case <-timer.C:
 | 
			
		||||
			t.Fatal("Test timeout waiting for eviction")
 | 
			
		||||
		case k := <-evictCh:
 | 
			
		||||
			timer.Stop()
 | 
			
		||||
			if !cmp.Equal(k, keysToFill[i]) {
 | 
			
		||||
				t.Fatalf("Evicted key %+v, wanted %+v", k, keysToFill[i])
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// TestAddCausesMultipleEvictions verifies the case where adding one new entry
 | 
			
		||||
// causes the eviction of multiple old entries to make space for the new one.
 | 
			
		||||
func TestAddCausesMultipleEvictions(t *testing.T) {
 | 
			
		||||
	evictCh := make(chan Key, defaultTestCacheSize)
 | 
			
		||||
	onEvicted := func(k Key, _ *Entry) {
 | 
			
		||||
		evictCh <- k
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	keysToFill := []Key{{Path: "a"}, {Path: "b"}, {Path: "c"}, {Path: "d"}, {Path: "e"}}
 | 
			
		||||
	keyCausingEviction := Key{Path: "abcde"}
 | 
			
		||||
 | 
			
		||||
	lru := NewLRU(defaultTestCacheSize, onEvicted)
 | 
			
		||||
	for _, key := range keysToFill {
 | 
			
		||||
		lru.Add(key, &Entry{})
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	lru.Add(keyCausingEviction, &Entry{})
 | 
			
		||||
 | 
			
		||||
	for i := range keysToFill {
 | 
			
		||||
		timer := time.NewTimer(defaultTestTimeout)
 | 
			
		||||
		select {
 | 
			
		||||
		case <-timer.C:
 | 
			
		||||
			t.Fatal("Test timeout waiting for eviction")
 | 
			
		||||
		case k := <-evictCh:
 | 
			
		||||
			timer.Stop()
 | 
			
		||||
			if !cmp.Equal(k, keysToFill[i]) {
 | 
			
		||||
				t.Fatalf("Evicted key %+v, wanted %+v", k, keysToFill[i])
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// TestModifyCausesMultipleEvictions verifies the case where mofiying an
 | 
			
		||||
// existing entry to increase its size leads to the eviction of older entries
 | 
			
		||||
// to make space for the new one.
 | 
			
		||||
func TestModifyCausesMultipleEvictions(t *testing.T) {
 | 
			
		||||
	evictCh := make(chan Key, defaultTestCacheSize)
 | 
			
		||||
	onEvicted := func(k Key, _ *Entry) {
 | 
			
		||||
		evictCh <- k
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	keysToFill := []Key{{Path: "a"}, {Path: "b"}, {Path: "c"}, {Path: "d"}, {Path: "e"}}
 | 
			
		||||
	lru := NewLRU(defaultTestCacheSize, onEvicted)
 | 
			
		||||
	for _, key := range keysToFill {
 | 
			
		||||
		lru.Add(key, &Entry{})
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	lru.Add(keysToFill[len(keysToFill)-1], &Entry{HeaderData: "xxxx"})
 | 
			
		||||
	for i := range keysToFill[:len(keysToFill)-1] {
 | 
			
		||||
		timer := time.NewTimer(defaultTestTimeout)
 | 
			
		||||
		select {
 | 
			
		||||
		case <-timer.C:
 | 
			
		||||
			t.Fatal("Test timeout waiting for eviction")
 | 
			
		||||
		case k := <-evictCh:
 | 
			
		||||
			timer.Stop()
 | 
			
		||||
			if !cmp.Equal(k, keysToFill[i]) {
 | 
			
		||||
				t.Fatalf("Evicted key %+v, wanted %+v", k, keysToFill[i])
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestLRUResize(t *testing.T) {
 | 
			
		||||
	tests := []struct {
 | 
			
		||||
		desc            string
 | 
			
		||||
		maxSize         int64
 | 
			
		||||
		keysToFill      []Key
 | 
			
		||||
		newMaxSize      int64
 | 
			
		||||
		wantEvictedKeys []Key
 | 
			
		||||
	}{
 | 
			
		||||
		{
 | 
			
		||||
			desc:            "resize causes multiple evictions",
 | 
			
		||||
			maxSize:         5,
 | 
			
		||||
			keysToFill:      []Key{{Path: "a"}, {Path: "b"}, {Path: "c"}, {Path: "d"}, {Path: "e"}},
 | 
			
		||||
			newMaxSize:      3,
 | 
			
		||||
			wantEvictedKeys: []Key{{Path: "a"}, {Path: "b"}},
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			desc:            "resize causes no evictions",
 | 
			
		||||
			maxSize:         50,
 | 
			
		||||
			keysToFill:      []Key{{Path: "a"}, {Path: "b"}, {Path: "c"}, {Path: "d"}, {Path: "e"}},
 | 
			
		||||
			newMaxSize:      10,
 | 
			
		||||
			wantEvictedKeys: []Key{},
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			desc:            "resize to higher value",
 | 
			
		||||
			maxSize:         5,
 | 
			
		||||
			keysToFill:      []Key{{Path: "a"}, {Path: "b"}, {Path: "c"}, {Path: "d"}, {Path: "e"}},
 | 
			
		||||
			newMaxSize:      10,
 | 
			
		||||
			wantEvictedKeys: []Key{},
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for _, test := range tests {
 | 
			
		||||
		t.Run(test.desc, func(t *testing.T) {
 | 
			
		||||
			var evictedKeys []Key
 | 
			
		||||
			onEvicted := func(k Key, _ *Entry) {
 | 
			
		||||
				evictedKeys = append(evictedKeys, k)
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			lru := NewLRU(test.maxSize, onEvicted)
 | 
			
		||||
			for _, key := range test.keysToFill {
 | 
			
		||||
				lru.Add(key, &Entry{})
 | 
			
		||||
			}
 | 
			
		||||
			lru.Resize(test.newMaxSize)
 | 
			
		||||
			if !cmp.Equal(evictedKeys, test.wantEvictedKeys, cmpopts.EquateEmpty()) {
 | 
			
		||||
				t.Fatalf("lru.Resize evicted keys {%v}, should have evicted {%v}", evictedKeys, test.wantEvictedKeys)
 | 
			
		||||
			}
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,276 @@
 | 
			
		|||
/*
 | 
			
		||||
 *
 | 
			
		||||
 * Copyright 2021 gRPC authors.
 | 
			
		||||
 *
 | 
			
		||||
 * Licensed under the Apache License, Version 2.0 (the "License");
 | 
			
		||||
 * you may not use this file except in compliance with the License.
 | 
			
		||||
 * You may obtain a copy of the License at
 | 
			
		||||
 *
 | 
			
		||||
 *     http://www.apache.org/licenses/LICENSE-2.0
 | 
			
		||||
 *
 | 
			
		||||
 * Unless required by applicable law or agreed to in writing, software
 | 
			
		||||
 * distributed under the License is distributed on an "AS IS" BASIS,
 | 
			
		||||
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
			
		||||
 * See the License for the specific language governing permissions and
 | 
			
		||||
 * limitations under the License.
 | 
			
		||||
 *
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
package rls
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"testing"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"github.com/google/go-cmp/cmp"
 | 
			
		||||
	"github.com/google/go-cmp/cmp/cmpopts"
 | 
			
		||||
	"google.golang.org/grpc/internal/backoff"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
var (
 | 
			
		||||
	cacheKeys = []cacheKey{
 | 
			
		||||
		{path: "0", keys: "a"},
 | 
			
		||||
		{path: "1", keys: "b"},
 | 
			
		||||
		{path: "2", keys: "c"},
 | 
			
		||||
		{path: "3", keys: "d"},
 | 
			
		||||
		{path: "4", keys: "e"},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	longDuration  = 10 * time.Minute
 | 
			
		||||
	shortDuration = 1 * time.Millisecond
 | 
			
		||||
	cacheEntries  []*cacheEntry
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func initCacheEntries() {
 | 
			
		||||
	// All entries have a dummy size of 1 to simplify resize operations.
 | 
			
		||||
	cacheEntries = []*cacheEntry{
 | 
			
		||||
		{
 | 
			
		||||
			// Entry is valid and minimum expiry time has not expired.
 | 
			
		||||
			expiryTime:        time.Now().Add(longDuration),
 | 
			
		||||
			earliestEvictTime: time.Now().Add(longDuration),
 | 
			
		||||
			size:              1,
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			// Entry is valid and is in backoff.
 | 
			
		||||
			expiryTime:   time.Now().Add(longDuration),
 | 
			
		||||
			backoffTime:  time.Now().Add(longDuration),
 | 
			
		||||
			backoffState: &backoffState{timer: time.NewTimer(longDuration)},
 | 
			
		||||
			size:         1,
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			// Entry is valid, and not in backoff.
 | 
			
		||||
			expiryTime: time.Now().Add(longDuration),
 | 
			
		||||
			size:       1,
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			// Entry is invalid.
 | 
			
		||||
			expiryTime: time.Time{}.Add(shortDuration),
 | 
			
		||||
			size:       1,
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			// Entry is invalid valid and backoff has expired.
 | 
			
		||||
			expiryTime:        time.Time{}.Add(shortDuration),
 | 
			
		||||
			backoffExpiryTime: time.Time{}.Add(shortDuration),
 | 
			
		||||
			size:              1,
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (s) TestLRU_BasicOperations(t *testing.T) {
 | 
			
		||||
	initCacheEntries()
 | 
			
		||||
	// Create an LRU and add some entries to it.
 | 
			
		||||
	lru := newLRU()
 | 
			
		||||
	for _, k := range cacheKeys {
 | 
			
		||||
		lru.addEntry(k)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Get the least recent entry. This should be the first entry we added.
 | 
			
		||||
	if got, want := lru.getLeastRecentlyUsed(), cacheKeys[0]; got != want {
 | 
			
		||||
		t.Fatalf("lru.getLeastRecentlyUsed() = %v, want %v", got, want)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Iterate through the slice of keys we added earlier, making them the most
 | 
			
		||||
	// recent entry, one at a time. The least recent entry at that point should
 | 
			
		||||
	// be the next entry from our slice of keys.
 | 
			
		||||
	for i, k := range cacheKeys {
 | 
			
		||||
		lru.makeRecent(k)
 | 
			
		||||
 | 
			
		||||
		lruIndex := (i + 1) % len(cacheKeys)
 | 
			
		||||
		if got, want := lru.getLeastRecentlyUsed(), cacheKeys[lruIndex]; got != want {
 | 
			
		||||
			t.Fatalf("lru.getLeastRecentlyUsed() = %v, want %v", got, want)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Iterate through the slice of keys we added earlier, removing them one at
 | 
			
		||||
	// a time The least recent entry at that point should be the next entry from
 | 
			
		||||
	// our slice of keys, except for the last one because the lru will be empty.
 | 
			
		||||
	for i, k := range cacheKeys {
 | 
			
		||||
		lru.removeEntry(k)
 | 
			
		||||
 | 
			
		||||
		var want cacheKey
 | 
			
		||||
		if i < len(cacheKeys)-1 {
 | 
			
		||||
			want = cacheKeys[i+1]
 | 
			
		||||
		}
 | 
			
		||||
		if got := lru.getLeastRecentlyUsed(); got != want {
 | 
			
		||||
			t.Fatalf("lru.getLeastRecentlyUsed() = %v, want %v", got, want)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (s) TestLRU_IterateAndRun(t *testing.T) {
 | 
			
		||||
	initCacheEntries()
 | 
			
		||||
	// Create an LRU and add some entries to it.
 | 
			
		||||
	lru := newLRU()
 | 
			
		||||
	for _, k := range cacheKeys {
 | 
			
		||||
		lru.addEntry(k)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Iterate through the lru to make sure that entries are returned in the
 | 
			
		||||
	// least recently used order.
 | 
			
		||||
	var gotKeys []cacheKey
 | 
			
		||||
	lru.iterateAndRun(func(key cacheKey) {
 | 
			
		||||
		gotKeys = append(gotKeys, key)
 | 
			
		||||
	})
 | 
			
		||||
	if !cmp.Equal(gotKeys, cacheKeys, cmp.AllowUnexported(cacheKey{})) {
 | 
			
		||||
		t.Fatalf("lru.iterateAndRun returned %v, want %v", gotKeys, cacheKeys)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Make sure that removing entries from the lru while iterating through it
 | 
			
		||||
	// is a safe operation.
 | 
			
		||||
	lru.iterateAndRun(func(key cacheKey) {
 | 
			
		||||
		lru.removeEntry(key)
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
	// Check the lru internals to make sure we freed up all the memory.
 | 
			
		||||
	if len := lru.ll.Len(); len != 0 {
 | 
			
		||||
		t.Fatalf("Number of entries in the lru's underlying list is %d, want 0", len)
 | 
			
		||||
	}
 | 
			
		||||
	if len := len(lru.m); len != 0 {
 | 
			
		||||
		t.Fatalf("Number of entries in the lru's underlying map is %d, want 0", len)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (s) TestDataCache_BasicOperations(t *testing.T) {
 | 
			
		||||
	initCacheEntries()
 | 
			
		||||
	dc := newDataCache(5, nil)
 | 
			
		||||
	for i, k := range cacheKeys {
 | 
			
		||||
		dc.addEntry(k, cacheEntries[i])
 | 
			
		||||
	}
 | 
			
		||||
	for i, k := range cacheKeys {
 | 
			
		||||
		entry := dc.getEntry(k)
 | 
			
		||||
		if !cmp.Equal(entry, cacheEntries[i], cmp.AllowUnexported(cacheEntry{}, backoffState{}), cmpopts.IgnoreUnexported(time.Timer{})) {
 | 
			
		||||
			t.Fatalf("Data cache lookup for key %v returned entry %v, want %v", k, entry, cacheEntries[i])
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (s) TestDataCache_AddForcesResize(t *testing.T) {
 | 
			
		||||
	initCacheEntries()
 | 
			
		||||
	dc := newDataCache(1, nil)
 | 
			
		||||
 | 
			
		||||
	// The first entry in cacheEntries has a minimum expiry time in the future.
 | 
			
		||||
	// This entry would stop the resize operation since we do not evict entries
 | 
			
		||||
	// whose minimum expiration time is in the future. So, we do not use that
 | 
			
		||||
	// entry in this test. The entry being added has a running backoff timer.
 | 
			
		||||
	evicted, ok := dc.addEntry(cacheKeys[1], cacheEntries[1])
 | 
			
		||||
	if evicted || !ok {
 | 
			
		||||
		t.Fatalf("dataCache.addEntry() returned (%v, %v) want (false, true)", evicted, ok)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Add another entry leading to the eviction of the above entry which has a
 | 
			
		||||
	// running backoff timer. The first return value is expected to be true.
 | 
			
		||||
	backoffCancelled, ok := dc.addEntry(cacheKeys[2], cacheEntries[2])
 | 
			
		||||
	if !backoffCancelled || !ok {
 | 
			
		||||
		t.Fatalf("dataCache.addEntry() returned (%v, %v) want (true, true)", backoffCancelled, ok)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Add another entry leading to the eviction of the above entry which does not
 | 
			
		||||
	// have a running backoff timer. This should evict the above entry, but the
 | 
			
		||||
	// first return value is expected to be false.
 | 
			
		||||
	backoffCancelled, ok = dc.addEntry(cacheKeys[3], cacheEntries[3])
 | 
			
		||||
	if backoffCancelled || !ok {
 | 
			
		||||
		t.Fatalf("dataCache.addEntry() returned (%v, %v) want (false, true)", backoffCancelled, ok)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (s) TestDataCache_Resize(t *testing.T) {
 | 
			
		||||
	initCacheEntries()
 | 
			
		||||
	dc := newDataCache(5, nil)
 | 
			
		||||
	for i, k := range cacheKeys {
 | 
			
		||||
		dc.addEntry(k, cacheEntries[i])
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// The first cache entry (with a key of cacheKeys[0]) that we added has an
 | 
			
		||||
	// earliestEvictTime in the future. As part of the resize operation, we
 | 
			
		||||
	// traverse the cache in least recently used order, and this will be first
 | 
			
		||||
	// entry that we will encounter. And since the earliestEvictTime is in the
 | 
			
		||||
	// future, the resize operation will stop, leaving the cache bigger than
 | 
			
		||||
	// what was asked for.
 | 
			
		||||
	if dc.resize(1) {
 | 
			
		||||
		t.Fatalf("dataCache.resize() returned true, want false")
 | 
			
		||||
	}
 | 
			
		||||
	if dc.currentSize != 5 {
 | 
			
		||||
		t.Fatalf("dataCache.size is %d, want 5", dc.currentSize)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Remove the entry with earliestEvictTime in the future and retry the
 | 
			
		||||
	// resize operation.
 | 
			
		||||
	dc.removeEntryForTesting(cacheKeys[0])
 | 
			
		||||
	if !dc.resize(1) {
 | 
			
		||||
		t.Fatalf("dataCache.resize() returned false, want true")
 | 
			
		||||
	}
 | 
			
		||||
	if dc.currentSize != 1 {
 | 
			
		||||
		t.Fatalf("dataCache.size is %d, want 1", dc.currentSize)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (s) TestDataCache_EvictExpiredEntries(t *testing.T) {
 | 
			
		||||
	initCacheEntries()
 | 
			
		||||
	dc := newDataCache(5, nil)
 | 
			
		||||
	for i, k := range cacheKeys {
 | 
			
		||||
		dc.addEntry(k, cacheEntries[i])
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// The last two entries in the cacheEntries list have expired, and will be
 | 
			
		||||
	// evicted. The first three should still remain in the cache.
 | 
			
		||||
	if !dc.evictExpiredEntries() {
 | 
			
		||||
		t.Fatal("dataCache.evictExpiredEntries() returned false, want true")
 | 
			
		||||
	}
 | 
			
		||||
	if dc.currentSize != 3 {
 | 
			
		||||
		t.Fatalf("dataCache.size is %d, want 3", dc.currentSize)
 | 
			
		||||
	}
 | 
			
		||||
	for i := 0; i < 3; i++ {
 | 
			
		||||
		entry := dc.getEntry(cacheKeys[i])
 | 
			
		||||
		if !cmp.Equal(entry, cacheEntries[i], cmp.AllowUnexported(cacheEntry{}, backoffState{}), cmpopts.IgnoreUnexported(time.Timer{})) {
 | 
			
		||||
			t.Fatalf("Data cache lookup for key %v returned entry %v, want %v", cacheKeys[i], entry, cacheEntries[i])
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (s) TestDataCache_ResetBackoffState(t *testing.T) {
 | 
			
		||||
	type fakeBackoff struct {
 | 
			
		||||
		backoff.Strategy
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	initCacheEntries()
 | 
			
		||||
	dc := newDataCache(5, nil)
 | 
			
		||||
	for i, k := range cacheKeys {
 | 
			
		||||
		dc.addEntry(k, cacheEntries[i])
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	newBackoffState := &backoffState{bs: &fakeBackoff{}}
 | 
			
		||||
	if updatePicker := dc.resetBackoffState(newBackoffState); !updatePicker {
 | 
			
		||||
		t.Fatal("dataCache.resetBackoffState() returned updatePicker is false, want true")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Make sure that the entry with no backoff state was not touched.
 | 
			
		||||
	if entry := dc.getEntry(cacheKeys[0]); cmp.Equal(entry.backoffState, newBackoffState, cmp.AllowUnexported(backoffState{})) {
 | 
			
		||||
		t.Fatal("dataCache.resetBackoffState() touched entries without a valid backoffState")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Make sure that the entry with a valid backoff state was reset.
 | 
			
		||||
	entry := dc.getEntry(cacheKeys[1])
 | 
			
		||||
	if diff := cmp.Diff(entry.backoffState, newBackoffState, cmp.AllowUnexported(backoffState{})); diff != "" {
 | 
			
		||||
		t.Fatalf("unexpected diff in backoffState for cache entry after dataCache.resetBackoffState(): %s", diff)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,112 @@
 | 
			
		|||
/*
 | 
			
		||||
 *
 | 
			
		||||
 * Copyright 2021 gRPC authors.
 | 
			
		||||
 *
 | 
			
		||||
 * Licensed under the Apache License, Version 2.0 (the "License");
 | 
			
		||||
 * you may not use this file except in compliance with the License.
 | 
			
		||||
 * You may obtain a copy of the License at
 | 
			
		||||
 *
 | 
			
		||||
 *     http://www.apache.org/licenses/LICENSE-2.0
 | 
			
		||||
 *
 | 
			
		||||
 * Unless required by applicable law or agreed to in writing, software
 | 
			
		||||
 * distributed under the License is distributed on an "AS IS" BASIS,
 | 
			
		||||
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
			
		||||
 * See the License for the specific language governing permissions and
 | 
			
		||||
 * limitations under the License.
 | 
			
		||||
 *
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
package rls
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"sync/atomic"
 | 
			
		||||
	"unsafe"
 | 
			
		||||
 | 
			
		||||
	"google.golang.org/grpc/balancer"
 | 
			
		||||
	"google.golang.org/grpc/balancer/base"
 | 
			
		||||
	"google.golang.org/grpc/connectivity"
 | 
			
		||||
	internalgrpclog "google.golang.org/grpc/internal/grpclog"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// TODO(easwars): Remove this once all RLS code is merged.
 | 
			
		||||
//lint:file-ignore U1000 Ignore all unused code, not all code is merged yet.
 | 
			
		||||
 | 
			
		||||
// childPolicyWrapper is a reference counted wrapper around a child policy.
 | 
			
		||||
//
 | 
			
		||||
// The LB policy maintains a map of these wrappers keyed by the target returned
 | 
			
		||||
// by RLS. When a target is seen for the first time, a child policy wrapper is
 | 
			
		||||
// created for it and the wrapper is added to the child policy map. Each entry
 | 
			
		||||
// in the data cache holds references to the corresponding child policy
 | 
			
		||||
// wrappers. The LB policy also holds a reference to the child policy wrapper
 | 
			
		||||
// for the default target specified in the LB Policy Configuration
 | 
			
		||||
//
 | 
			
		||||
// When a cache entry is evicted, it releases references to the child policy
 | 
			
		||||
// wrappers that it contains. When all references have been released, the
 | 
			
		||||
// wrapper is removed from the child policy map and is destroyed.
 | 
			
		||||
//
 | 
			
		||||
// The child policy wrapper also caches the connectivity state and most recent
 | 
			
		||||
// picker from the child policy. Once the child policy wrapper reports
 | 
			
		||||
// TRANSIENT_FAILURE, it will continue reporting that state until it goes READY;
 | 
			
		||||
// transitions from TRANSIENT_FAILURE to CONNECTING are ignored.
 | 
			
		||||
//
 | 
			
		||||
// Whenever a child policy wrapper changes its connectivity state, the LB policy
 | 
			
		||||
// returns a new picker to the channel, since the channel may need to re-process
 | 
			
		||||
// the picks for queued RPCs.
 | 
			
		||||
//
 | 
			
		||||
// It is not safe for concurrent access.
 | 
			
		||||
type childPolicyWrapper struct {
 | 
			
		||||
	logger *internalgrpclog.PrefixLogger
 | 
			
		||||
	target string // RLS target corresponding to this child policy.
 | 
			
		||||
	refCnt int    // Reference count.
 | 
			
		||||
 | 
			
		||||
	// Balancer state reported by the child policy. The RLS LB policy maintains
 | 
			
		||||
	// these child policies in a BalancerGroup. The state reported by the child
 | 
			
		||||
	// policy is pushed to the state aggregator (which is also implemented by the
 | 
			
		||||
	// RLS LB policy) and cached here. See handleChildPolicyStateUpdate() for
 | 
			
		||||
	// details on how the state aggregation is performed.
 | 
			
		||||
	//
 | 
			
		||||
	// While this field is written to by the LB policy, it is read by the picker
 | 
			
		||||
	// at Pick time. Making this an atomic to enable the picker to read this value
 | 
			
		||||
	// without a mutex.
 | 
			
		||||
	state unsafe.Pointer // *balancer.State
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// newChildPolicyWrapper creates a child policy wrapper for the given target,
 | 
			
		||||
// and is initialized with one reference and starts off in CONNECTING state.
 | 
			
		||||
func newChildPolicyWrapper(target string) *childPolicyWrapper {
 | 
			
		||||
	c := &childPolicyWrapper{
 | 
			
		||||
		target: target,
 | 
			
		||||
		refCnt: 1,
 | 
			
		||||
		state: unsafe.Pointer(&balancer.State{
 | 
			
		||||
			ConnectivityState: connectivity.Connecting,
 | 
			
		||||
			Picker:            base.NewErrPicker(balancer.ErrNoSubConnAvailable),
 | 
			
		||||
		}),
 | 
			
		||||
	}
 | 
			
		||||
	c.logger = internalgrpclog.NewPrefixLogger(logger, fmt.Sprintf("[rls-child-policy-wrapper %s %p] ", c.target, c))
 | 
			
		||||
	c.logger.Infof("Created")
 | 
			
		||||
	return c
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// acquireRef increments the reference count on the child policy wrapper.
 | 
			
		||||
func (c *childPolicyWrapper) acquireRef() {
 | 
			
		||||
	c.refCnt++
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// releaseRef decrements the reference count on the child policy wrapper. The
 | 
			
		||||
// return value indicates whether the released reference was the last one.
 | 
			
		||||
func (c *childPolicyWrapper) releaseRef() bool {
 | 
			
		||||
	c.refCnt--
 | 
			
		||||
	return c.refCnt == 0
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// lamify causes the child policy wrapper to return a picker which will always
 | 
			
		||||
// fail requests. This is used when the wrapper runs into errors when trying to
 | 
			
		||||
// build and parse the child policy configuration.
 | 
			
		||||
func (c *childPolicyWrapper) lamify(err error) {
 | 
			
		||||
	c.logger.Warningf("Entering lame mode: %v", err)
 | 
			
		||||
	atomic.StorePointer(&c.state, unsafe.Pointer(&balancer.State{
 | 
			
		||||
		ConnectivityState: connectivity.TransientFailure,
 | 
			
		||||
		Picker:            base.NewErrPicker(err),
 | 
			
		||||
	}))
 | 
			
		||||
}
 | 
			
		||||
		Loading…
	
		Reference in New Issue