google.golang.org/grpc@v1.62.1/xds/internal/xdsclient/singleton_test.go (about) 1 /* 2 * 3 * Copyright 2022 gRPC authors. 4 * 5 * Licensed under the Apache License, Version 2.0 (the "License"); 6 * you may not use this file except in compliance with the License. 7 * You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 * 17 */ 18 19 package xdsclient 20 21 import ( 22 "context" 23 "testing" 24 25 "github.com/google/uuid" 26 "google.golang.org/grpc/internal/testutils" 27 "google.golang.org/grpc/internal/testutils/xds/bootstrap" 28 ) 29 30 // Test that multiple New() returns the same Client. And only when the last 31 // client is closed, the underlying client is closed. 32 func (s) TestClientNewSingleton(t *testing.T) { 33 // Create a bootstrap configuration, place it in a file in the temp 34 // directory, and set the bootstrap env vars to point to it. 35 nodeID := uuid.New().String() 36 cleanup, err := bootstrap.CreateFile(bootstrap.Options{ 37 NodeID: nodeID, 38 ServerURI: "non-existent-server-address", 39 }) 40 if err != nil { 41 t.Fatal(err) 42 } 43 defer cleanup() 44 45 // Override the singleton creation hook to get notified. 46 origSingletonClientImplCreateHook := singletonClientImplCreateHook 47 singletonCreationCh := testutils.NewChannel() 48 singletonClientImplCreateHook = func() { 49 singletonCreationCh.Replace(nil) 50 } 51 defer func() { singletonClientImplCreateHook = origSingletonClientImplCreateHook }() 52 53 // Override the singleton close hook to get notified. 54 origSingletonClientImplCloseHook := singletonClientImplCloseHook 55 singletonCloseCh := testutils.NewChannel() 56 singletonClientImplCloseHook = func() { 57 singletonCloseCh.Replace(nil) 58 } 59 defer func() { singletonClientImplCloseHook = origSingletonClientImplCloseHook }() 60 61 // The first call to New() should create a new singleton client. 62 _, closeFunc, err := New() 63 if err != nil { 64 t.Fatalf("failed to create xDS client: %v", err) 65 } 66 67 ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout) 68 defer cancel() 69 if _, err := singletonCreationCh.Receive(ctx); err != nil { 70 t.Fatalf("Timeout when waiting for singleton xDS client to be created: %v", err) 71 } 72 73 // Calling New() again should not create new singleton client implementations. 74 const count = 9 75 closeFuncs := make([]func(), 9) 76 for i := 0; i < count; i++ { 77 func() { 78 _, closeFuncs[i], err = New() 79 if err != nil { 80 t.Fatalf("%d-th call to New() failed with error: %v", i, err) 81 } 82 83 sCtx, sCancel := context.WithTimeout(ctx, defaultTestShortTimeout) 84 defer sCancel() 85 if _, err := singletonCreationCh.Receive(sCtx); err == nil { 86 t.Fatalf("%d-th call to New() created a new singleton client", i) 87 } 88 }() 89 } 90 91 // Call Close() multiple times on each of the clients created in the above for 92 // loop. Close() calls are idempotent, and the underlying client 93 // implementation will not be closed until we release the first reference we 94 // acquired above, via the first call to New(). 95 for i := 0; i < count; i++ { 96 func() { 97 closeFuncs[i]() 98 closeFuncs[i]() 99 100 sCtx, sCancel := context.WithTimeout(ctx, defaultTestShortTimeout) 101 defer sCancel() 102 if _, err := singletonCloseCh.Receive(sCtx); err == nil { 103 t.Fatal("singleton client implementation closed before all references are released") 104 } 105 }() 106 } 107 108 // Call the last Close(). The underlying implementation should be closed. 109 closeFunc() 110 if _, err := singletonCloseCh.Receive(ctx); err != nil { 111 t.Fatalf("Timeout waiting for singleton client implementation to be closed: %v", err) 112 } 113 114 // Calling New() again, after the previous Client was actually closed, should 115 // create a new one. 116 _, closeFunc, err = New() 117 if err != nil { 118 t.Fatalf("failed to create client: %v", err) 119 } 120 defer closeFunc() 121 if _, err := singletonCreationCh.Receive(ctx); err != nil { 122 t.Fatalf("Timeout when waiting for singleton xDS client to be created: %v", err) 123 } 124 }