google.golang.org/grpc@v1.72.2/xds/internal/xdsclient/client_refcounted_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 "sync" 24 "testing" 25 26 "github.com/google/uuid" 27 "google.golang.org/grpc/internal/testutils" 28 "google.golang.org/grpc/internal/testutils/stats" 29 "google.golang.org/grpc/internal/testutils/xds/e2e" 30 "google.golang.org/grpc/internal/xds/bootstrap" 31 ) 32 33 // Tests that multiple calls to New() with the same name returns the same 34 // client. Also verifies that only when all references to the newly created 35 // client are released, the underlying client is closed. 36 func (s) TestClientNew_Single(t *testing.T) { 37 // Create a bootstrap configuration, place it in a file in the temp 38 // directory, and set the bootstrap env vars to point to it. 39 nodeID := uuid.New().String() 40 contents := e2e.DefaultBootstrapContents(t, nodeID, "non-existent-server-address") 41 config, err := bootstrap.NewConfigFromContents(contents) 42 if err != nil { 43 t.Fatalf("Failed to parse bootstrap contents: %s, %v", contents, err) 44 } 45 pool := NewPool(config) 46 47 // Override the client creation hook to get notified. 48 origClientImplCreateHook := xdsClientImplCreateHook 49 clientImplCreateCh := testutils.NewChannel() 50 xdsClientImplCreateHook = func(name string) { 51 clientImplCreateCh.Replace(name) 52 } 53 defer func() { xdsClientImplCreateHook = origClientImplCreateHook }() 54 55 // Override the client close hook to get notified. 56 origClientImplCloseHook := xdsClientImplCloseHook 57 clientImplCloseCh := testutils.NewChannel() 58 xdsClientImplCloseHook = func(name string) { 59 clientImplCloseCh.Replace(name) 60 } 61 defer func() { xdsClientImplCloseHook = origClientImplCloseHook }() 62 63 // The first call to New() should create a new client. 64 _, closeFunc, err := pool.NewClient(t.Name(), &stats.NoopMetricsRecorder{}) 65 if err != nil { 66 t.Fatalf("Failed to create xDS client: %v", err) 67 } 68 69 ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout) 70 defer cancel() 71 if _, err := clientImplCreateCh.Receive(ctx); err != nil { 72 t.Fatalf("Timeout when waiting for xDS client to be created: %v", err) 73 } 74 75 // Calling New() again should not create new client implementations. 76 const count = 9 77 closeFuncs := make([]func(), count) 78 for i := 0; i < count; i++ { 79 func() { 80 _, closeFuncs[i], err = pool.NewClient(t.Name(), &stats.NoopMetricsRecorder{}) 81 if err != nil { 82 t.Fatalf("%d-th call to New() failed with error: %v", i, err) 83 } 84 85 sCtx, sCancel := context.WithTimeout(ctx, defaultTestShortTimeout) 86 defer sCancel() 87 if _, err := clientImplCreateCh.Receive(sCtx); err == nil { 88 t.Fatalf("%d-th call to New() created a new client", i) 89 } 90 }() 91 } 92 93 // Call Close() multiple times on each of the clients created in the above 94 // for loop. Close() calls are idempotent, and the underlying client 95 // implementation will not be closed until we release the first reference we 96 // acquired above, via the first call to New(). 97 for i := 0; i < count; i++ { 98 func() { 99 closeFuncs[i]() 100 closeFuncs[i]() 101 102 sCtx, sCancel := context.WithTimeout(ctx, defaultTestShortTimeout) 103 defer sCancel() 104 if _, err := clientImplCloseCh.Receive(sCtx); err == nil { 105 t.Fatal("Client implementation closed before all references are released") 106 } 107 }() 108 } 109 110 // Call the last Close(). The underlying implementation should be closed. 111 closeFunc() 112 if _, err := clientImplCloseCh.Receive(ctx); err != nil { 113 t.Fatalf("Timeout waiting for client implementation to be closed: %v", err) 114 } 115 116 // Calling New() again, after the previous Client was actually closed, 117 // should create a new one. 118 _, closeFunc, err = pool.NewClient(t.Name(), &stats.NoopMetricsRecorder{}) 119 if err != nil { 120 t.Fatalf("Failed to create xDS client: %v", err) 121 } 122 defer closeFunc() 123 if _, err := clientImplCreateCh.Receive(ctx); err != nil { 124 t.Fatalf("Timeout when waiting for xDS client to be created: %v", err) 125 } 126 } 127 128 // Tests the scenario where there are multiple calls to New() with different 129 // names. Verifies that reference counts are tracked correctly for each client 130 // and that only when all references are released for a client, it is closed. 131 func (s) TestClientNew_Multiple(t *testing.T) { 132 // Create a bootstrap configuration, place it in a file in the temp 133 // directory, and set the bootstrap env vars to point to it. 134 nodeID := uuid.New().String() 135 contents := e2e.DefaultBootstrapContents(t, nodeID, "non-existent-server-address") 136 config, err := bootstrap.NewConfigFromContents(contents) 137 if err != nil { 138 t.Fatalf("Failed to parse bootstrap contents: %s, %v", contents, err) 139 } 140 pool := NewPool(config) 141 142 // Override the client creation hook to get notified. 143 origClientImplCreateHook := xdsClientImplCreateHook 144 clientImplCreateCh := testutils.NewChannel() 145 xdsClientImplCreateHook = func(name string) { 146 clientImplCreateCh.Replace(name) 147 } 148 defer func() { xdsClientImplCreateHook = origClientImplCreateHook }() 149 150 // Override the client close hook to get notified. 151 origClientImplCloseHook := xdsClientImplCloseHook 152 clientImplCloseCh := testutils.NewChannel() 153 xdsClientImplCloseHook = func(name string) { 154 clientImplCloseCh.Replace(name) 155 } 156 defer func() { xdsClientImplCloseHook = origClientImplCloseHook }() 157 158 // Create two xDS clients. 159 client1Name := t.Name() + "-1" 160 _, closeFunc1, err := pool.NewClient(client1Name, &stats.NoopMetricsRecorder{}) 161 if err != nil { 162 t.Fatalf("Failed to create xDS client: %v", err) 163 } 164 ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout) 165 defer cancel() 166 name, err := clientImplCreateCh.Receive(ctx) 167 if err != nil { 168 t.Fatalf("Timeout when waiting for xDS client to be created: %v", err) 169 } 170 if name.(string) != client1Name { 171 t.Fatalf("xDS client created for name %q, want %q", name.(string), client1Name) 172 } 173 174 client2Name := t.Name() + "-2" 175 _, closeFunc2, err := pool.NewClient(client2Name, &stats.NoopMetricsRecorder{}) 176 if err != nil { 177 t.Fatalf("Failed to create xDS client: %v", err) 178 } 179 name, err = clientImplCreateCh.Receive(ctx) 180 if err != nil { 181 t.Fatalf("Timeout when waiting for xDS client to be created: %v", err) 182 } 183 if name.(string) != client2Name { 184 t.Fatalf("xDS client created for name %q, want %q", name.(string), client1Name) 185 } 186 187 // Create N more references to each of these clients. 188 const count = 9 189 closeFuncs1 := make([]func(), count) 190 closeFuncs2 := make([]func(), count) 191 var wg sync.WaitGroup 192 wg.Add(2) 193 go func() { 194 defer wg.Done() 195 for i := 0; i < count; i++ { 196 var err error 197 _, closeFuncs1[i], err = pool.NewClient(client1Name, &stats.NoopMetricsRecorder{}) 198 if err != nil { 199 t.Errorf("%d-th call to New() failed with error: %v", i, err) 200 } 201 } 202 }() 203 go func() { 204 defer wg.Done() 205 for i := 0; i < count; i++ { 206 var err error 207 _, closeFuncs2[i], err = pool.NewClient(client2Name, &stats.NoopMetricsRecorder{}) 208 if err != nil { 209 t.Errorf("%d-th call to New() failed with error: %v", i, err) 210 } 211 } 212 }() 213 wg.Wait() 214 if t.Failed() { 215 t.FailNow() 216 } 217 218 // Ensure that none of the create hooks are called. 219 sCtx, sCancel := context.WithTimeout(ctx, defaultTestShortTimeout) 220 defer sCancel() 221 if _, err := clientImplCreateCh.Receive(sCtx); err == nil { 222 t.Fatalf("New xDS client created when expected to reuse an existing one") 223 } 224 225 // The close function returned by New() is idempotent and calling it 226 // multiple times should not decrement the reference count multiple times. 227 for i := 0; i < count; i++ { 228 closeFuncs1[i]() 229 closeFuncs1[i]() 230 } 231 sCtx, sCancel = context.WithTimeout(ctx, defaultTestShortTimeout) 232 defer sCancel() 233 if _, err := clientImplCloseCh.Receive(sCtx); err == nil { 234 t.Fatal("Client implementation closed before all references are released") 235 } 236 237 // Release the last reference and verify that the client is closed 238 // completely. 239 closeFunc1() 240 name, err = clientImplCloseCh.Receive(ctx) 241 if err != nil { 242 t.Fatal("Timeout when waiting for xDS client to be closed completely") 243 } 244 if name.(string) != client1Name { 245 t.Fatalf("xDS client closed for name %q, want %q", name.(string), client1Name) 246 } 247 248 // Ensure that the close hook is not called for the second client. 249 sCtx, sCancel = context.WithTimeout(ctx, defaultTestShortTimeout) 250 defer sCancel() 251 if _, err := clientImplCloseCh.Receive(sCtx); err == nil { 252 t.Fatal("Client implementation closed before all references are released") 253 } 254 255 // The close function returned by New() is idempotent and calling it 256 // multiple times should not decrement the reference count multiple times. 257 for i := 0; i < count; i++ { 258 closeFuncs2[i]() 259 closeFuncs2[i]() 260 } 261 sCtx, sCancel = context.WithTimeout(ctx, defaultTestShortTimeout) 262 defer sCancel() 263 if _, err := clientImplCloseCh.Receive(sCtx); err == nil { 264 t.Fatal("Client implementation closed before all references are released") 265 } 266 267 // Release the last reference and verify that the client is closed 268 // completely. 269 closeFunc2() 270 name, err = clientImplCloseCh.Receive(ctx) 271 if err != nil { 272 t.Fatal("Timeout when waiting for xDS client to be closed completely") 273 } 274 if name.(string) != client2Name { 275 t.Fatalf("xDS client closed for name %q, want %q", name.(string), client2Name) 276 } 277 }