/* * * Copyright 2022 gRPC authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ package test import ( "context" "fmt" "strings" "testing" "time" "google.golang.org/grpc" "google.golang.org/grpc/codes" "google.golang.org/grpc/credentials/insecure" "google.golang.org/grpc/internal/channelz" "google.golang.org/grpc/resolver/manual" "google.golang.org/grpc/status" testgrpc "google.golang.org/grpc/test/grpc_testing" testpb "google.golang.org/grpc/test/grpc_testing" ) // TestClientConnClose_WithPendingRPC tests the scenario where the channel has // not yet received any update from the name resolver and hence RPCs are // blocking. The test verifies that closing the ClientConn unblocks the RPC with // the expected error code. func (s) TestClientConnClose_WithPendingRPC(t *testing.T) { // Initialize channelz. Used to determine pending RPC count. czCleanup := channelz.NewChannelzStorageForTesting() defer czCleanupWrapper(czCleanup, t) r := manual.NewBuilderWithScheme("whatever") cc, err := grpc.Dial(r.Scheme()+":///test.server", grpc.WithTransportCredentials(insecure.NewCredentials()), grpc.WithResolvers(r)) if err != nil { t.Fatalf("grpc.Dial() failed: %v", err) } client := testgrpc.NewTestServiceClient(cc) ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout) defer cancel() doneErrCh := make(chan error, 1) go func() { // This RPC would block until the ClientConn is closed, because the // resolver has not provided its first update yet. _, err := client.EmptyCall(ctx, &testpb.Empty{}) if status.Code(err) != codes.Canceled || !strings.Contains(err.Error(), "client connection is closing") { doneErrCh <- fmt.Errorf("EmptyCall() = %v, want %s", err, codes.Canceled) } doneErrCh <- nil }() // Make sure that there is one pending RPC on the ClientConn before attempting // to close it. If we don't do this, cc.Close() can happen before the above // goroutine gets to make the RPC. for { if err := ctx.Err(); err != nil { t.Fatal(err) } tcs, _ := channelz.GetTopChannels(0, 0) if len(tcs) != 1 { t.Fatalf("there should only be one top channel, not %d", len(tcs)) } started := tcs[0].ChannelData.CallsStarted completed := tcs[0].ChannelData.CallsSucceeded + tcs[0].ChannelData.CallsFailed if (started - completed) == 1 { break } time.Sleep(defaultTestShortTimeout) } cc.Close() if err := <-doneErrCh; err != nil { t.Fatal(err) } }