Skip to content

Commit b88744b

Browse files
authored
xds: add ConfigSelector to support RouteAction timeouts (#3991)
1 parent 20636e7 commit b88744b

11 files changed

+774
-96
lines changed

benchmark/primitives/primitives_test.go

+26
Original file line numberDiff line numberDiff line change
@@ -399,3 +399,29 @@ func runStructTypeAssertion(b *testing.B, fer interface{}) {
399399
b.Fatal("error")
400400
}
401401
}
402+
403+
func BenchmarkWaitGroupAddDone(b *testing.B) {
404+
wg := sync.WaitGroup{}
405+
b.RunParallel(func(pb *testing.PB) {
406+
i := 0
407+
for ; pb.Next(); i++ {
408+
wg.Add(1)
409+
}
410+
for ; i > 0; i-- {
411+
wg.Done()
412+
}
413+
})
414+
}
415+
416+
func BenchmarkRLockUnlock(b *testing.B) {
417+
mu := sync.RWMutex{}
418+
b.RunParallel(func(pb *testing.PB) {
419+
i := 0
420+
for ; pb.Next(); i++ {
421+
mu.RLock()
422+
}
423+
for ; i > 0; i-- {
424+
mu.RUnlock()
425+
}
426+
})
427+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
/*
2+
*
3+
* Copyright 2017 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+
// Benchmark options for safe config selector type.
20+
21+
package primitives_test
22+
23+
import (
24+
"sync"
25+
"sync/atomic"
26+
"testing"
27+
"time"
28+
"unsafe"
29+
)
30+
31+
type safeUpdaterAtomicAndCounter struct {
32+
ptr unsafe.Pointer // *countingFunc
33+
}
34+
35+
type countingFunc struct {
36+
mu sync.RWMutex
37+
f func()
38+
}
39+
40+
func (s *safeUpdaterAtomicAndCounter) call() {
41+
cfPtr := atomic.LoadPointer(&s.ptr)
42+
var cf *countingFunc
43+
for {
44+
cf = (*countingFunc)(cfPtr)
45+
cf.mu.RLock()
46+
cfPtr2 := atomic.LoadPointer(&s.ptr)
47+
if cfPtr == cfPtr2 {
48+
// Use cf with confidence!
49+
break
50+
}
51+
// cf changed; try to use the new one instead, because the old one is
52+
// no longer valid to use.
53+
cf.mu.RUnlock()
54+
cfPtr = cfPtr2
55+
}
56+
defer cf.mu.RUnlock()
57+
cf.f()
58+
}
59+
60+
func (s *safeUpdaterAtomicAndCounter) update(f func()) {
61+
newCF := &countingFunc{f: f}
62+
oldCFPtr := atomic.SwapPointer(&s.ptr, unsafe.Pointer(newCF))
63+
if oldCFPtr == nil {
64+
return
65+
}
66+
(*countingFunc)(oldCFPtr).mu.Lock()
67+
(*countingFunc)(oldCFPtr).mu.Unlock() //lint:ignore SA2001 necessary to unlock after locking to unblock any RLocks
68+
}
69+
70+
type safeUpdaterRWMutex struct {
71+
mu sync.RWMutex
72+
f func()
73+
}
74+
75+
func (s *safeUpdaterRWMutex) call() {
76+
s.mu.RLock()
77+
defer s.mu.RUnlock()
78+
s.f()
79+
}
80+
81+
func (s *safeUpdaterRWMutex) update(f func()) {
82+
s.mu.Lock()
83+
defer s.mu.Unlock()
84+
s.f = f
85+
}
86+
87+
type updater interface {
88+
call()
89+
update(f func())
90+
}
91+
92+
func benchmarkSafeUpdater(b *testing.B, u updater) {
93+
t := time.NewTicker(time.Second)
94+
go func() {
95+
for range t.C {
96+
u.update(func() {})
97+
}
98+
}()
99+
b.RunParallel(func(pb *testing.PB) {
100+
u.update(func() {})
101+
for pb.Next() {
102+
u.call()
103+
}
104+
})
105+
t.Stop()
106+
}
107+
108+
func BenchmarkSafeUpdaterAtomicAndCounter(b *testing.B) {
109+
benchmarkSafeUpdater(b, &safeUpdaterAtomicAndCounter{})
110+
}
111+
112+
func BenchmarkSafeUpdaterRWMutex(b *testing.B) {
113+
benchmarkSafeUpdater(b, &safeUpdaterRWMutex{})
114+
}

clientconn.go

+49-16
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ import (
3838
"google.golang.org/grpc/internal/channelz"
3939
"google.golang.org/grpc/internal/grpcsync"
4040
"google.golang.org/grpc/internal/grpcutil"
41+
iresolver "google.golang.org/grpc/internal/resolver"
4142
"google.golang.org/grpc/internal/transport"
4243
"google.golang.org/grpc/keepalive"
4344
"google.golang.org/grpc/resolver"
@@ -104,6 +105,17 @@ func Dial(target string, opts ...DialOption) (*ClientConn, error) {
104105
return DialContext(context.Background(), target, opts...)
105106
}
106107

108+
type defaultConfigSelector struct {
109+
sc *ServiceConfig
110+
}
111+
112+
func (dcs *defaultConfigSelector) SelectConfig(rpcInfo iresolver.RPCInfo) *iresolver.RPCConfig {
113+
return &iresolver.RPCConfig{
114+
Context: rpcInfo.Context,
115+
MethodConfig: getMethodConfig(dcs.sc, rpcInfo.Method),
116+
}
117+
}
118+
107119
// DialContext creates a client connection to the given target. By default, it's
108120
// a non-blocking dial (the function won't wait for connections to be
109121
// established, and connecting happens in the background). To make it a blocking
@@ -224,6 +236,7 @@ func DialContext(ctx context.Context, target string, opts ...DialOption) (conn *
224236
case sc, ok := <-cc.dopts.scChan:
225237
if ok {
226238
cc.sc = &sc
239+
cc.safeConfigSelector.UpdateConfigSelector(&defaultConfigSelector{&sc})
227240
scSet = true
228241
}
229242
default:
@@ -273,6 +286,7 @@ func DialContext(ctx context.Context, target string, opts ...DialOption) (conn *
273286
case sc, ok := <-cc.dopts.scChan:
274287
if ok {
275288
cc.sc = &sc
289+
cc.safeConfigSelector.UpdateConfigSelector(&defaultConfigSelector{&sc})
276290
}
277291
case <-ctx.Done():
278292
return nil, ctx.Err()
@@ -479,6 +493,8 @@ type ClientConn struct {
479493
balancerBuildOpts balancer.BuildOptions
480494
blockingpicker *pickerWrapper
481495

496+
safeConfigSelector iresolver.SafeConfigSelector
497+
482498
mu sync.RWMutex
483499
resolverWrapper *ccResolverWrapper
484500
sc *ServiceConfig
@@ -539,6 +555,7 @@ func (cc *ClientConn) scWatcher() {
539555
// TODO: load balance policy runtime change is ignored.
540556
// We may revisit this decision in the future.
541557
cc.sc = &sc
558+
cc.safeConfigSelector.UpdateConfigSelector(&defaultConfigSelector{&sc})
542559
cc.mu.Unlock()
543560
case <-cc.ctx.Done():
544561
return
@@ -577,13 +594,13 @@ func init() {
577594

578595
func (cc *ClientConn) maybeApplyDefaultServiceConfig(addrs []resolver.Address) {
579596
if cc.sc != nil {
580-
cc.applyServiceConfigAndBalancer(cc.sc, addrs)
597+
cc.applyServiceConfigAndBalancer(cc.sc, nil, addrs)
581598
return
582599
}
583600
if cc.dopts.defaultServiceConfig != nil {
584-
cc.applyServiceConfigAndBalancer(cc.dopts.defaultServiceConfig, addrs)
601+
cc.applyServiceConfigAndBalancer(cc.dopts.defaultServiceConfig, &defaultConfigSelector{cc.dopts.defaultServiceConfig}, addrs)
585602
} else {
586-
cc.applyServiceConfigAndBalancer(emptyServiceConfig, addrs)
603+
cc.applyServiceConfigAndBalancer(emptyServiceConfig, &defaultConfigSelector{emptyServiceConfig}, addrs)
587604
}
588605
}
589606

@@ -620,7 +637,15 @@ func (cc *ClientConn) updateResolverState(s resolver.State, err error) error {
620637
// default, per the error handling design?
621638
} else {
622639
if sc, ok := s.ServiceConfig.Config.(*ServiceConfig); s.ServiceConfig.Err == nil && ok {
623-
cc.applyServiceConfigAndBalancer(sc, s.Addresses)
640+
configSelector := iresolver.GetConfigSelector(s)
641+
if configSelector != nil {
642+
if len(s.ServiceConfig.Config.(*ServiceConfig).Methods) != 0 {
643+
channelz.Infof(logger, cc.channelzID, "method configs in service config will be ignored due to presence of config selector")
644+
}
645+
} else {
646+
configSelector = &defaultConfigSelector{sc}
647+
}
648+
cc.applyServiceConfigAndBalancer(sc, configSelector, s.Addresses)
624649
} else {
625650
ret = balancer.ErrBadResolverState
626651
if cc.balancerWrapper == nil {
@@ -630,6 +655,7 @@ func (cc *ClientConn) updateResolverState(s resolver.State, err error) error {
630655
} else {
631656
err = status.Errorf(codes.Unavailable, "illegal service config type: %T", s.ServiceConfig.Config)
632657
}
658+
cc.safeConfigSelector.UpdateConfigSelector(&defaultConfigSelector{cc.sc})
633659
cc.blockingpicker.updatePicker(base.NewErrPicker(err))
634660
cc.csMgr.updateState(connectivity.TransientFailure)
635661
cc.mu.Unlock()
@@ -864,6 +890,20 @@ func (ac *addrConn) tryUpdateAddrs(addrs []resolver.Address) bool {
864890
return curAddrFound
865891
}
866892

893+
func getMethodConfig(sc *ServiceConfig, method string) MethodConfig {
894+
if sc == nil {
895+
return MethodConfig{}
896+
}
897+
if m, ok := sc.Methods[method]; ok {
898+
return m
899+
}
900+
i := strings.LastIndex(method, "/")
901+
if m, ok := sc.Methods[method[:i+1]]; ok {
902+
return m
903+
}
904+
return sc.Methods[""]
905+
}
906+
867907
// GetMethodConfig gets the method config of the input method.
868908
// If there's an exact match for input method (i.e. /service/method), we return
869909
// the corresponding MethodConfig.
@@ -876,17 +916,7 @@ func (cc *ClientConn) GetMethodConfig(method string) MethodConfig {
876916
// TODO: Avoid the locking here.
877917
cc.mu.RLock()
878918
defer cc.mu.RUnlock()
879-
if cc.sc == nil {
880-
return MethodConfig{}
881-
}
882-
if m, ok := cc.sc.Methods[method]; ok {
883-
return m
884-
}
885-
i := strings.LastIndex(method, "/")
886-
if m, ok := cc.sc.Methods[method[:i+1]]; ok {
887-
return m
888-
}
889-
return cc.sc.Methods[""]
919+
return getMethodConfig(cc.sc, method)
890920
}
891921

892922
func (cc *ClientConn) healthCheckConfig() *healthCheckConfig {
@@ -909,12 +939,15 @@ func (cc *ClientConn) getTransport(ctx context.Context, failfast bool, method st
909939
return t, done, nil
910940
}
911941

912-
func (cc *ClientConn) applyServiceConfigAndBalancer(sc *ServiceConfig, addrs []resolver.Address) {
942+
func (cc *ClientConn) applyServiceConfigAndBalancer(sc *ServiceConfig, configSelector iresolver.ConfigSelector, addrs []resolver.Address) {
913943
if sc == nil {
914944
// should never reach here.
915945
return
916946
}
917947
cc.sc = sc
948+
if configSelector != nil {
949+
cc.safeConfigSelector.UpdateConfigSelector(configSelector)
950+
}
918951

919952
if cc.sc.retryThrottling != nil {
920953
newThrottler := &retryThrottler{

internal/resolver/config_selector.go

+93
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
/*
2+
*
3+
* Copyright 2020 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 resolver provides internal resolver-related functionality.
20+
package resolver
21+
22+
import (
23+
"context"
24+
"sync"
25+
26+
"google.golang.org/grpc/internal/serviceconfig"
27+
"google.golang.org/grpc/resolver"
28+
)
29+
30+
// ConfigSelector controls what configuration to use for every RPC.
31+
type ConfigSelector interface {
32+
// Selects the configuration for the RPC.
33+
SelectConfig(RPCInfo) *RPCConfig
34+
}
35+
36+
// RPCInfo contains RPC information needed by a ConfigSelector.
37+
type RPCInfo struct {
38+
// Context is the user's context for the RPC and contains headers and
39+
// application timeout. It is passed for interception purposes and for
40+
// efficiency reasons. SelectConfig should not be blocking.
41+
Context context.Context
42+
Method string // i.e. "/Service/Method"
43+
}
44+
45+
// RPCConfig describes the configuration to use for each RPC.
46+
type RPCConfig struct {
47+
// The context to use for the remainder of the RPC; can pass info to LB
48+
// policy or affect timeout or metadata.
49+
Context context.Context
50+
MethodConfig serviceconfig.MethodConfig // configuration to use for this RPC
51+
OnCommitted func() // Called when the RPC has been committed (retries no longer possible)
52+
}
53+
54+
type csKeyType string
55+
56+
const csKey = csKeyType("grpc.internal.resolver.configSelector")
57+
58+
// SetConfigSelector sets the config selector in state and returns the new
59+
// state.
60+
func SetConfigSelector(state resolver.State, cs ConfigSelector) resolver.State {
61+
state.Attributes = state.Attributes.WithValues(csKey, cs)
62+
return state
63+
}
64+
65+
// GetConfigSelector retrieves the config selector from state, if present, and
66+
// returns it or nil if absent.
67+
func GetConfigSelector(state resolver.State) ConfigSelector {
68+
cs, _ := state.Attributes.Value(csKey).(ConfigSelector)
69+
return cs
70+
}
71+
72+
// SafeConfigSelector allows for safe switching of ConfigSelector
73+
// implementations such that previous values are guaranteed to not be in use
74+
// when UpdateConfigSelector returns.
75+
type SafeConfigSelector struct {
76+
mu sync.RWMutex
77+
cs ConfigSelector
78+
}
79+
80+
// UpdateConfigSelector swaps to the provided ConfigSelector and blocks until
81+
// all uses of the previous ConfigSelector have completed.
82+
func (scs *SafeConfigSelector) UpdateConfigSelector(cs ConfigSelector) {
83+
scs.mu.Lock()
84+
defer scs.mu.Unlock()
85+
scs.cs = cs
86+
}
87+
88+
// SelectConfig defers to the current ConfigSelector in scs.
89+
func (scs *SafeConfigSelector) SelectConfig(r RPCInfo) *RPCConfig {
90+
scs.mu.RLock()
91+
defer scs.mu.RUnlock()
92+
return scs.cs.SelectConfig(r)
93+
}

0 commit comments

Comments
 (0)