google.golang.org/grpc@v1.72.2/resolver_balancer_ext_test.go (about) 1 /* 2 * 3 * Copyright 2023 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 grpc_test 20 21 import ( 22 "context" 23 "errors" 24 "fmt" 25 "runtime" 26 "strings" 27 "testing" 28 "time" 29 30 "google.golang.org/grpc" 31 "google.golang.org/grpc/balancer" 32 "google.golang.org/grpc/connectivity" 33 "google.golang.org/grpc/credentials/insecure" 34 "google.golang.org/grpc/internal" 35 "google.golang.org/grpc/internal/balancer/stub" 36 "google.golang.org/grpc/internal/channelz" 37 "google.golang.org/grpc/resolver" 38 "google.golang.org/grpc/resolver/manual" 39 ) 40 41 // TestResolverBalancerInteraction tests: 42 // 1. resolver.Builder.Build() -> 43 // 2. resolver.ClientConn.UpdateState() -> 44 // 3. balancer.Balancer.UpdateClientConnState() -> 45 // 4. balancer.ClientConn.ResolveNow() -> 46 // 5. resolver.Resolver.ResolveNow() -> 47 func (s) TestResolverBalancerInteraction(t *testing.T) { 48 name := strings.ReplaceAll(strings.ToLower(t.Name()), "/", "") 49 bf := stub.BalancerFuncs{ 50 UpdateClientConnState: func(bd *stub.BalancerData, ccs balancer.ClientConnState) error { 51 bd.ClientConn.ResolveNow(resolver.ResolveNowOptions{}) 52 return nil 53 }, 54 } 55 stub.Register(name, bf) 56 57 rb := manual.NewBuilderWithScheme(name) 58 rb.BuildCallback = func(_ resolver.Target, cc resolver.ClientConn, _ resolver.BuildOptions) { 59 sc := cc.ParseServiceConfig(`{"loadBalancingConfig": [{"` + name + `":{}}]}`) 60 cc.UpdateState(resolver.State{ 61 Addresses: []resolver.Address{{Addr: "test"}}, 62 ServiceConfig: sc, 63 }) 64 } 65 rnCh := make(chan struct{}) 66 rb.ResolveNowCallback = func(resolver.ResolveNowOptions) { close(rnCh) } 67 resolver.Register(rb) 68 69 cc, err := grpc.NewClient(name+":///", grpc.WithTransportCredentials(insecure.NewCredentials())) 70 if err != nil { 71 t.Fatalf("grpc.NewClient error: %v", err) 72 } 73 defer cc.Close() 74 cc.Connect() 75 select { 76 case <-rnCh: 77 case <-time.After(defaultTestTimeout): 78 t.Fatalf("timed out waiting for resolver.ResolveNow") 79 } 80 } 81 82 type resolverBuilderWithErr struct { 83 resolver.Resolver 84 errCh <-chan error 85 scheme string 86 } 87 88 func (b *resolverBuilderWithErr) Build(_ resolver.Target, cc resolver.ClientConn, _ resolver.BuildOptions) (resolver.Resolver, error) { 89 if err := <-b.errCh; err != nil { 90 return nil, err 91 } 92 return b, nil 93 } 94 95 func (b *resolverBuilderWithErr) Scheme() string { 96 return b.scheme 97 } 98 99 func (b *resolverBuilderWithErr) Close() {} 100 101 // TestResolverBuildFailure tests: 102 // 1. resolver.Builder.Build() passes. 103 // 2. Channel enters idle mode. 104 // 3. An RPC happens. 105 // 4. resolver.Builder.Build() fails. 106 func (s) TestResolverBuildFailure(t *testing.T) { 107 enterIdle := internal.EnterIdleModeForTesting.(func(*grpc.ClientConn)) 108 name := strings.ReplaceAll(strings.ToLower(t.Name()), "/", "") 109 resErrCh := make(chan error, 1) 110 resolver.Register(&resolverBuilderWithErr{errCh: resErrCh, scheme: name}) 111 112 resErrCh <- nil 113 cc, err := grpc.NewClient(name+":///", grpc.WithTransportCredentials(insecure.NewCredentials())) 114 if err != nil { 115 t.Fatalf("grpc.NewClient error: %v", err) 116 } 117 defer cc.Close() 118 cc.Connect() 119 enterIdle(cc) 120 const errStr = "test error from resolver builder" 121 t.Log("pushing res err") 122 resErrCh <- errors.New(errStr) 123 ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout) 124 defer cancel() 125 if err := cc.Invoke(ctx, "/a/b", nil, nil); err == nil || !strings.Contains(err.Error(), errStr) { 126 t.Fatalf("Invoke = %v; want %v", err, errStr) 127 } 128 } 129 130 // TestEnterIdleDuringResolverUpdateState tests a scenario that used to deadlock 131 // while calling UpdateState at the same time as the resolver being closed while 132 // the channel enters idle mode. 133 func (s) TestEnterIdleDuringResolverUpdateState(t *testing.T) { 134 enterIdle := internal.EnterIdleModeForTesting.(func(*grpc.ClientConn)) 135 name := strings.ReplaceAll(strings.ToLower(t.Name()), "/", "") 136 137 // Create a manual resolver that spams UpdateState calls until it is closed. 138 rb := manual.NewBuilderWithScheme(name) 139 var cancel context.CancelFunc 140 rb.BuildCallback = func(_ resolver.Target, cc resolver.ClientConn, _ resolver.BuildOptions) { 141 var ctx context.Context 142 ctx, cancel = context.WithCancel(context.Background()) 143 go func() { 144 for ctx.Err() == nil { 145 cc.UpdateState(resolver.State{Addresses: []resolver.Address{{Addr: "test"}}}) 146 } 147 }() 148 } 149 rb.CloseCallback = func() { 150 cancel() 151 } 152 resolver.Register(rb) 153 154 cc, err := grpc.NewClient(name+":///", grpc.WithTransportCredentials(insecure.NewCredentials())) 155 if err != nil { 156 t.Fatalf("grpc.NewClient error: %v", err) 157 } 158 defer cc.Close() 159 160 // Enter/exit idle mode repeatedly. 161 for i := 0; i < 2000; i++ { 162 // Start a timer so we panic out of the deadlock and can see all the 163 // stack traces to debug the problem. 164 p := time.AfterFunc(time.Second, func() { 165 buf := make([]byte, 8192) 166 buf = buf[0:runtime.Stack(buf, true)] 167 t.Error("Timed out waiting for enterIdle") 168 panic(fmt.Sprint("Stack trace:\n", string(buf))) 169 }) 170 enterIdle(cc) 171 p.Stop() 172 cc.Connect() 173 } 174 } 175 176 // TestEnterIdleDuringBalancerUpdateState tests calling UpdateState at the same 177 // time as the balancer being closed while the channel enters idle mode. 178 func (s) TestEnterIdleDuringBalancerUpdateState(t *testing.T) { 179 enterIdle := internal.EnterIdleModeForTesting.(func(*grpc.ClientConn)) 180 name := strings.ReplaceAll(strings.ToLower(t.Name()), "/", "") 181 182 // Create a balancer that calls UpdateState once asynchronously, attempting 183 // to make the channel appear ready even after entering idle. 184 bf := stub.BalancerFuncs{ 185 UpdateClientConnState: func(bd *stub.BalancerData, ccs balancer.ClientConnState) error { 186 go func() { 187 bd.ClientConn.UpdateState(balancer.State{ConnectivityState: connectivity.Ready}) 188 }() 189 return nil 190 }, 191 } 192 stub.Register(name, bf) 193 194 rb := manual.NewBuilderWithScheme(name) 195 rb.BuildCallback = func(_ resolver.Target, cc resolver.ClientConn, _ resolver.BuildOptions) { 196 cc.UpdateState(resolver.State{Addresses: []resolver.Address{{Addr: "test"}}}) 197 } 198 resolver.Register(rb) 199 200 cc, err := grpc.NewClient( 201 name+":///", 202 grpc.WithTransportCredentials(insecure.NewCredentials()), 203 grpc.WithDefaultServiceConfig(`{"loadBalancingConfig": [{"`+name+`":{}}]}`)) 204 if err != nil { 205 t.Fatalf("grpc.NewClient error: %v", err) 206 } 207 defer cc.Close() 208 209 // Enter/exit idle mode repeatedly. 210 for i := 0; i < 2000; i++ { 211 enterIdle(cc) 212 if got, want := cc.GetState(), connectivity.Idle; got != want { 213 t.Fatalf("cc state = %v; want %v", got, want) 214 } 215 cc.Connect() 216 } 217 } 218 219 // TestEnterIdleDuringBalancerNewSubConn tests calling NewSubConn at the same 220 // time as the balancer being closed while the channel enters idle mode. 221 func (s) TestEnterIdleDuringBalancerNewSubConn(t *testing.T) { 222 channelz.TurnOn() 223 defer internal.ChannelzTurnOffForTesting() 224 enterIdle := internal.EnterIdleModeForTesting.(func(*grpc.ClientConn)) 225 name := strings.ReplaceAll(strings.ToLower(t.Name()), "/", "") 226 227 // Create a balancer that calls NewSubConn once asynchronously, attempting 228 // to create a subchannel after going idle. 229 bf := stub.BalancerFuncs{ 230 UpdateClientConnState: func(bd *stub.BalancerData, ccs balancer.ClientConnState) error { 231 go func() { 232 bd.ClientConn.NewSubConn([]resolver.Address{{Addr: "test"}}, balancer.NewSubConnOptions{}) 233 }() 234 return nil 235 }, 236 } 237 stub.Register(name, bf) 238 239 rb := manual.NewBuilderWithScheme(name) 240 rb.BuildCallback = func(_ resolver.Target, cc resolver.ClientConn, _ resolver.BuildOptions) { 241 cc.UpdateState(resolver.State{Addresses: []resolver.Address{{Addr: "test"}}}) 242 } 243 resolver.Register(rb) 244 245 cc, err := grpc.NewClient( 246 name+":///", 247 grpc.WithTransportCredentials(insecure.NewCredentials()), 248 grpc.WithDefaultServiceConfig(`{"loadBalancingConfig": [{"`+name+`":{}}]}`)) 249 if err != nil { 250 t.Fatalf("grpc.NewClient error: %v", err) 251 } 252 defer cc.Close() 253 254 // Enter/exit idle mode repeatedly. 255 for i := 0; i < 2000; i++ { 256 enterIdle(cc) 257 tcs, _ := channelz.GetTopChannels(0, 0) 258 if len(tcs) != 1 { 259 t.Fatalf("Found channels: %v; expected 1 entry", tcs) 260 } 261 if got := tcs[0].SubChans(); len(got) != 0 { 262 t.Fatalf("Found subchannels: %v; expected 0 entries", got) 263 } 264 cc.Connect() 265 } 266 }