Skip to content

Commit d855f2e

Browse files
committed
resolve comments
1 parent 7f62ecc commit d855f2e

2 files changed

Lines changed: 75 additions & 131 deletions

File tree

internal/xds/resolver/serviceconfig.go

Lines changed: 18 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -213,34 +213,28 @@ func (cs *configSelector) SelectConfig(rpcInfo iresolver.RPCInfo) (*iresolver.RP
213213
// Add a ref to the selected cluster, as this RPC needs this
214214
// cluster until it is committed.
215215
info.refCount.Add(1)
216-
var once sync.Once
217-
config.OnCommitted = func() {
218-
once.Do(func() {
219-
if v := info.refCount.Add(-1); v == 0 {
220-
// We call unsubscribe rather than sendNewServiceConfig to
221-
// prevent redundant updates. If the reference count in the
222-
// dependency manager drops to zero, it will automatically
223-
// trigger a service config update with this cluster
224-
// removed. Calling unsubscribe allows the dependency
225-
// manager to handle the update flow once and for all.
226-
info.unsubscribe()
227-
}
228-
})
229-
}
216+
config.OnCommitted = sync.OnceFunc(func() {
217+
if v := info.refCount.Add(-1); v == 0 {
218+
// We call unsubscribe rather than sendNewServiceConfig to
219+
// prevent redundant updates. If the reference count in the
220+
// dependency manager drops to zero, it will automatically
221+
// trigger a service config update with this cluster
222+
// removed. Calling unsubscribe allows the dependency
223+
// manager to handle the update flow once and for all.
224+
info.unsubscribe()
225+
}
226+
})
230227
} else if info, ok := cs.plugins[cluster.name]; ok {
231228
// Add a ref to the selected plugin, as this RPC needs this
232229
// plugin until it is committed.
233230
info.refCount.Add(1)
234-
var once sync.Once
235-
config.OnCommitted = func() {
236-
once.Do(func() {
237-
if v := info.refCount.Add(-1); v == 0 {
238-
// This entry will be removed from activePlugins when
239-
// producing a new service config update.
240-
cs.sendNewServiceConfig()
241-
}
242-
})
243-
}
231+
config.OnCommitted = sync.OnceFunc(func() {
232+
if v := info.refCount.Add(-1); v == 0 {
233+
// This entry will be removed from activePlugins when
234+
// producing a new service config update.
235+
cs.sendNewServiceConfig()
236+
}
237+
})
244238
}
245239

246240
if rt.maxStreamDuration != 0 {

test/xds/xds_resolver_e2e_test.go

Lines changed: 57 additions & 107 deletions
Original file line numberDiff line numberDiff line change
@@ -51,16 +51,16 @@ import (
5151
testpb "google.golang.org/grpc/interop/grpc_testing"
5252
)
5353

54-
// interceptingBuilder wraps a resolver.Builder and intercepts the ClientConn
54+
// wrappingBuilder wraps a resolver.Builder and intercepts the ClientConn
5555
// passed to Build by wrapping it with an interceptingClientConn.
56-
type interceptingBuilder struct {
56+
type wrappingBuilder struct {
5757
resolver.Builder
5858
jsonCh chan string
5959
}
6060

6161
// Build wraps the provided resolver.ClientConn with an interceptingClientConn
6262
// before delegating to the underlying resolver.Builder.
63-
func (ib *interceptingBuilder) Build(target resolver.Target, cc resolver.ClientConn, opts resolver.BuildOptions) (resolver.Resolver, error) {
63+
func (ib *wrappingBuilder) Build(target resolver.Target, cc resolver.ClientConn, opts resolver.BuildOptions) (resolver.Resolver, error) {
6464
icc := &interceptingClientConn{
6565
ClientConn: cc,
6666
jsonCh: ib.jsonCh,
@@ -149,6 +149,19 @@ func (i *testInterceptor) NewStream(ctx context.Context, _ iresolver.RPCInfo, ne
149149

150150
func (i *testInterceptor) Close() {}
151151

152+
func newTestFilterBuilder(t testing.TB, typeURL string) *testFilterBuilder {
153+
tb := &testFilterBuilder{
154+
typeURL: typeURL,
155+
blockChan: make(chan struct{}),
156+
enteredChan: make(chan struct{}, 1),
157+
}
158+
httpfilter.Register(tb)
159+
t.Cleanup(func() {
160+
httpfilter.UnregisterForTesting(typeURL)
161+
})
162+
return tb
163+
}
164+
152165
// wantServiceConfig returns a JSON representation of a service config with
153166
// xds_cluster_manager_experimental LB policy with child policies of
154167
// cds_experimental for the provided cluster names.
@@ -190,19 +203,23 @@ func compareJSONConfigs(t *testing.T, gotJSON, wantJSON string) {
190203
}
191204
}
192205

206+
func verifyServiceConfig(ctx context.Context, t *testing.T, jsonCh chan string, wantClusters ...string) {
207+
t.Helper()
208+
select {
209+
case <-ctx.Done():
210+
t.Fatal("Timeout waiting for resolver state update")
211+
case js := <-jsonCh:
212+
compareJSONConfigs(t, js, wantServiceConfig(wantClusters...))
213+
}
214+
}
215+
193216
// Test verifies that when RPCs are in flight holding references to an old
194217
// cluster, that cluster remains in the service config until all in-flight
195218
// RPCs finish.
196219
func (s) TestResolverDelayedClusterRemoval_MultipleInFlightRPCs(t *testing.T) {
197220
testFilterTypeURL := t.Name()
198-
blockChan, enteredChan := make(chan struct{}), make(chan struct{}, 2)
199-
tb := &testFilterBuilder{
200-
typeURL: testFilterTypeURL,
201-
blockChan: blockChan,
202-
enteredChan: enteredChan,
203-
}
204-
httpfilter.Register(tb)
205-
defer httpfilter.UnregisterForTesting(tb.typeURL)
221+
tb := newTestFilterBuilder(t, testFilterTypeURL)
222+
blockChan, enteredChan := tb.blockChan, tb.enteredChan
206223

207224
// Start an xDS management server.
208225
mgmtServer := e2e.StartManagementServer(t, e2e.ManagementServerOptions{})
@@ -217,8 +234,8 @@ func (s) TestResolverDelayedClusterRemoval_MultipleInFlightRPCs(t *testing.T) {
217234
}
218235

219236
// Create an intercepting resolver builder.
220-
jsonCh := make(chan string, 4)
221-
ib := &interceptingBuilder{
237+
jsonCh := make(chan string, 3)
238+
ib := &wrappingBuilder{
222239
Builder: r,
223240
jsonCh: jsonCh,
224241
}
@@ -286,10 +303,10 @@ func (s) TestResolverDelayedClusterRemoval_MultipleInFlightRPCs(t *testing.T) {
286303

287304
// Trigger first RPC to cluster A. This RPC should reach our custom HTTP
288305
// filter, and get blocked.
289-
rpc1Err := make(chan error, 1)
306+
errCh := make(chan error, 2)
290307
go func() {
291308
_, err := client.EmptyCall(ctx, &testpb.Empty{})
292-
rpc1Err <- err
309+
errCh <- err
293310
}()
294311

295312
// Verify that first RPC has entered the HTTP filter's NewStream and is
@@ -302,19 +319,13 @@ func (s) TestResolverDelayedClusterRemoval_MultipleInFlightRPCs(t *testing.T) {
302319

303320
// Read the first state update from the intercepting resolver.
304321
// This should contain cluster-A.
305-
select {
306-
case <-ctx.Done():
307-
t.Fatal("Timeout waiting for first resolver state update")
308-
case js := <-jsonCh:
309-
compareJSONConfigs(t, js, wantServiceConfig(clusterA))
310-
}
322+
verifyServiceConfig(ctx, t, jsonCh, clusterA)
311323

312324
// Trigger second RPC to cluster A. This RPC should reach our custom HTTP
313325
// filter, and get blocked.
314-
rpc2Err := make(chan error, 1)
315326
go func() {
316327
_, err := client.EmptyCall(ctx, &testpb.Empty{})
317-
rpc2Err <- err
328+
errCh <- err
318329
}()
319330

320331
// Verify that second RPC has entered the HTTP filter's NewStream and is
@@ -334,29 +345,17 @@ func (s) TestResolverDelayedClusterRemoval_MultipleInFlightRPCs(t *testing.T) {
334345
t.Fatal(err)
335346
}
336347

337-
select {
338-
case <-ctx.Done():
339-
t.Fatal("Timeout waiting for second resolver state update")
340-
case js := <-jsonCh:
341-
compareJSONConfigs(t, js, wantServiceConfig(clusterA, clusterB))
342-
}
348+
verifyServiceConfig(ctx, t, jsonCh, clusterA, clusterB)
343349

344350
// Unblock one of the RPCs and verify it completes successfully.
345351
blockChan <- struct{}{}
346-
var remainingRPCErr chan error
347352
select {
348353
case <-ctx.Done():
349354
t.Fatal("Timeout waiting for an RPC to succeed")
350-
case err := <-rpc1Err:
355+
case err := <-errCh:
351356
if err != nil {
352357
t.Fatalf("RPC failed with unexpected error: %v", err)
353358
}
354-
remainingRPCErr = rpc2Err
355-
case err := <-rpc2Err:
356-
if err != nil {
357-
t.Fatalf("RPC failed with unexpected error: %v", err)
358-
}
359-
remainingRPCErr = rpc1Err
360359
}
361360

362361
// Verify that because the other RPC to cluster-A is still in flight, the
@@ -373,20 +372,15 @@ func (s) TestResolverDelayedClusterRemoval_MultipleInFlightRPCs(t *testing.T) {
373372
select {
374373
case <-ctx.Done():
375374
t.Fatal("Timeout waiting for remaining RPC to succeed")
376-
case err := <-remainingRPCErr:
375+
case err := <-errCh:
377376
if err != nil {
378377
t.Fatalf("Remaining RPC failed with unexpected error: %v", err)
379378
}
380379
}
381380

382381
// Once the second RPC finishes (refCount drops 1 -> 0), cluster-A should be
383382
// removed from the service config.
384-
select {
385-
case <-ctx.Done():
386-
t.Fatal("Timeout waiting for third resolver state update")
387-
case js := <-jsonCh:
388-
compareJSONConfigs(t, js, wantServiceConfig(clusterB))
389-
}
383+
verifyServiceConfig(ctx, t, jsonCh, clusterB)
390384
}
391385

392386
// Test verifies that if stream creation fails early inside an HTTP filter or
@@ -396,15 +390,9 @@ func (s) TestResolverDelayedClusterRemoval_MultipleInFlightRPCs(t *testing.T) {
396390
func (s) TestResolverPrunesCluster_StreamCreationFailure(t *testing.T) {
397391
testFilterTypeURL := t.Name()
398392
const wantErr = "blocking filter error"
399-
blockChan, enteredChan := make(chan struct{}), make(chan struct{})
400-
tb := &testFilterBuilder{
401-
typeURL: testFilterTypeURL,
402-
blockChan: blockChan,
403-
enteredChan: enteredChan,
404-
newStreamErr: status.Error(codes.Unavailable, wantErr),
405-
}
406-
httpfilter.Register(tb)
407-
defer httpfilter.UnregisterForTesting(tb.typeURL)
393+
tb := newTestFilterBuilder(t, testFilterTypeURL)
394+
tb.newStreamErr = status.Error(codes.Unavailable, wantErr)
395+
close(tb.blockChan) // Close blockChan immediately so that the RPC fails without blocking
408396

409397
// Start an xDS management server.
410398
mgmtServer := e2e.StartManagementServer(t, e2e.ManagementServerOptions{})
@@ -420,7 +408,7 @@ func (s) TestResolverPrunesCluster_StreamCreationFailure(t *testing.T) {
420408

421409
// Intercept resolver builder.
422410
jsonCh := make(chan string, 3)
423-
ib := &interceptingBuilder{
411+
ib := &wrappingBuilder{
424412
Builder: r,
425413
jsonCh: jsonCh,
426414
}
@@ -480,34 +468,20 @@ func (s) TestResolverPrunesCluster_StreamCreationFailure(t *testing.T) {
480468
// Create a gRPC client using the xDS resolver.
481469
cc, err := grpc.NewClient(fmt.Sprintf("xds:///%s", serviceName), grpc.WithTransportCredentials(insecure.NewCredentials()), grpc.WithResolvers(ib))
482470
if err != nil {
483-
t.Fatalf("Failed to dial: %v", err)
471+
t.Fatalf("Failed to create a gRPC client: %v", err)
484472
}
485473
defer cc.Close()
486-
487-
// Trigger a RPC to cluster A. This RPC should reach our custom HTTP filter,
488-
// and get blocked.
489-
client := testgrpc.NewTestServiceClient(cc)
490-
rpcErrCh := make(chan error, 1)
491-
go func() {
492-
_, err := client.EmptyCall(ctx, &testpb.Empty{})
493-
rpcErrCh <- err
494-
}()
474+
cc.Connect()
495475

496476
// Read the first state update from the intercepting resolver.
497477
// This should contain cluster-A.
498-
select {
499-
case <-ctx.Done():
500-
t.Fatal("Timeout waiting for first resolver state update")
501-
case js := <-jsonCh:
502-
compareJSONConfigs(t, js, wantServiceConfig(clusterA))
503-
}
478+
verifyServiceConfig(ctx, t, jsonCh, clusterA)
504479

505-
// Verify that the RPC has entered the HTTP filter's NewStream and is
506-
// currently blocked.
507-
select {
508-
case <-enteredChan:
509-
case <-ctx.Done():
510-
t.Fatal("Timeout waiting for RPC to reach HTTP filter")
480+
// Make an RPC. Since the filter immediately fails, this should fail with
481+
// the expected error.
482+
client := testgrpc.NewTestServiceClient(cc)
483+
if _, err = client.EmptyCall(ctx, &testpb.Empty{}); status.Code(err) != codes.Unavailable || !strings.Contains(err.Error(), wantErr) {
484+
t.Fatalf("RPC failed with error %v, want code %v with desc containing %q", err, codes.Unavailable, wantErr)
511485
}
512486

513487
// Update the route configuration on the management server to point
@@ -520,36 +494,12 @@ func (s) TestResolverPrunesCluster_StreamCreationFailure(t *testing.T) {
520494
t.Fatal(err)
521495
}
522496

523-
// Read the second state update from the intercepting resolver.
524-
// This should contain both cluster-A and cluster-B, since the blocked
525-
// RPC holds a reference to cluster-A.
526-
select {
527-
case <-ctx.Done():
528-
t.Fatal("Timeout waiting for second resolver state update")
529-
case js := <-jsonCh:
530-
compareJSONConfigs(t, js, wantServiceConfig(clusterA, clusterB))
531-
}
532-
533-
// Unblock the filter's NewStream.
534-
close(blockChan)
497+
// Read the second state update from the intercepting resolver. This should
498+
// contain both cluster-A and cluster-B.
499+
verifyServiceConfig(ctx, t, jsonCh, clusterA, clusterB)
535500

536-
// Verify the RPC returns the expected blocking filter error.
537-
select {
538-
case <-ctx.Done():
539-
t.Fatal("Timeout waiting for RPC to fail")
540-
case err := <-rpcErrCh:
541-
if status.Code(err) != codes.Unavailable || !strings.Contains(err.Error(), wantErr) {
542-
t.Fatalf("RPC failed with error %v, want code %v with desc containing %q", err, codes.Unavailable, wantErr)
543-
}
544-
}
545-
546-
// Once the stream fails early, the resolver should unsubscribe from
547-
// cluster A and trigger a service config update that deletes the old
548-
// cluster. Read the third state update and ensure cluster-A is pruned.
549-
select {
550-
case <-ctx.Done():
551-
t.Fatal("Timeout waiting for third resolver state update")
552-
case js := <-jsonCh:
553-
compareJSONConfigs(t, js, wantServiceConfig(clusterB))
554-
}
501+
// Read the third state update from the intercepting resolver. Cluster-A's
502+
// reference count drops to 0, causing the resolver to unsubscribe and
503+
// prune it.
504+
verifyServiceConfig(ctx, t, jsonCh, clusterB)
555505
}

0 commit comments

Comments
 (0)