Merge pull request #23827 from apolcyn/bump_to_1_31_1

Bump 1.31.x to 1.31.1
diff --git a/src/compiler/ruby_generator_string-inl.h b/src/compiler/ruby_generator_string-inl.h
index b3e1938..968f795 100644
--- a/src/compiler/ruby_generator_string-inl.h
+++ b/src/compiler/ruby_generator_string-inl.h
@@ -124,7 +124,7 @@
     ReplacePrefix(&proto_type, ".", "");  // remove the leading . (no package)
     proto_type = RubyPackage(descriptor->file()) + "." + proto_type;
   }
-  std::string res(proto_type);
+  std::string res("." + proto_type);
   if (res.find('.') == std::string::npos) {
     return res;
   } else {
diff --git a/src/ruby/bin/math_services_pb.rb b/src/ruby/bin/math_services_pb.rb
index e6f32e3..961117c 100644
--- a/src/ruby/bin/math_services_pb.rb
+++ b/src/ruby/bin/math_services_pb.rb
@@ -31,19 +31,19 @@
 
       # Div divides DivArgs.dividend by DivArgs.divisor and returns the quotient
       # and remainder.
-      rpc :Div, DivArgs, DivReply
+      rpc :Div, ::Math::DivArgs, ::Math::DivReply
       # DivMany accepts an arbitrary number of division args from the client stream
       # and sends back the results in the reply stream.  The stream continues until
       # the client closes its end; the server does the same after sending all the
       # replies.  The stream ends immediately if either end aborts.
-      rpc :DivMany, stream(DivArgs), stream(DivReply)
+      rpc :DivMany, stream(::Math::DivArgs), stream(::Math::DivReply)
       # Fib generates numbers in the Fibonacci sequence.  If FibArgs.limit > 0, Fib
       # generates up to limit numbers; otherwise it continues until the call is
       # canceled.  Unlike Fib above, Fib has no final FibReply.
-      rpc :Fib, FibArgs, stream(Num)
+      rpc :Fib, ::Math::FibArgs, stream(::Math::Num)
       # Sum sums a stream of numbers, returning the final result once the stream
       # is closed.
-      rpc :Sum, stream(Num), Num
+      rpc :Sum, stream(::Math::Num), ::Math::Num
     end
 
     Stub = Service.rpc_stub_class
diff --git a/src/ruby/end2end/package_with_underscore_checker.rb b/src/ruby/end2end/package_with_underscore_checker.rb
index 27ea00f..043e621 100644
--- a/src/ruby/end2end/package_with_underscore_checker.rb
+++ b/src/ruby/end2end/package_with_underscore_checker.rb
@@ -43,8 +43,8 @@
   end
 
   correct_modularized_rpc = 'rpc :TestOne, ' \
-                            'Grpc::Testing::PackageWithUnderscore::Data::Request, ' \
-                            'Grpc::Testing::PackageWithUnderscore::Data::Response'
+                            '::Grpc::Testing::PackageWithUnderscore::Data::Request, ' \
+                            '::Grpc::Testing::PackageWithUnderscore::Data::Response'
 
   return if got.include?(correct_modularized_rpc)
 
diff --git a/src/ruby/lib/grpc/generic/client_stub.rb b/src/ruby/lib/grpc/generic/client_stub.rb
index b193f5c..1884dcb 100644
--- a/src/ruby/lib/grpc/generic/client_stub.rb
+++ b/src/ruby/lib/grpc/generic/client_stub.rb
@@ -100,7 +100,7 @@
                    channel_args: {},
                    interceptors: [])
       @ch = ClientStub.setup_channel(channel_override, host, creds,
-                                     channel_args)
+                                     channel_args.dup)
       alt_host = channel_args[Core::Channel::SSL_TARGET]
       @host = alt_host.nil? ? host : alt_host
       @propagate_mask = propagate_mask
diff --git a/src/ruby/pb/grpc/health/v1/health_services_pb.rb b/src/ruby/pb/grpc/health/v1/health_services_pb.rb
index 5992f1c..351e7e1 100644
--- a/src/ruby/pb/grpc/health/v1/health_services_pb.rb
+++ b/src/ruby/pb/grpc/health/v1/health_services_pb.rb
@@ -36,7 +36,7 @@
 
           # If the requested service is unknown, the call will fail with status
           # NOT_FOUND.
-          rpc :Check, HealthCheckRequest, HealthCheckResponse
+          rpc :Check, ::Grpc::Health::V1::HealthCheckRequest, ::Grpc::Health::V1::HealthCheckResponse
           # Performs a watch for the serving status of the requested service.
           # The server will immediately send back a message indicating the current
           # serving status.  It will then subsequently send a new message whenever
@@ -52,7 +52,7 @@
           # should assume this method is not supported and should not retry the
           # call.  If the call terminates with any other status (including OK),
           # clients should retry the call with appropriate exponential backoff.
-          rpc :Watch, HealthCheckRequest, stream(HealthCheckResponse)
+          rpc :Watch, ::Grpc::Health::V1::HealthCheckRequest, stream(::Grpc::Health::V1::HealthCheckResponse)
         end
 
         Stub = Service.rpc_stub_class
diff --git a/src/ruby/pb/src/proto/grpc/testing/messages_pb.rb b/src/ruby/pb/src/proto/grpc/testing/messages_pb.rb
index f492ccf..d902ae0 100644
--- a/src/ruby/pb/src/proto/grpc/testing/messages_pb.rb
+++ b/src/ruby/pb/src/proto/grpc/testing/messages_pb.rb
@@ -71,6 +71,10 @@
     add_message "grpc.testing.LoadBalancerStatsResponse" do
       map :rpcs_by_peer, :string, :int32, 1
       optional :num_failures, :int32, 2
+      map :rpcs_by_method, :string, :message, 3, "grpc.testing.LoadBalancerStatsResponse.RpcsByPeer"
+    end
+    add_message "grpc.testing.LoadBalancerStatsResponse.RpcsByPeer" do
+      map :rpcs_by_peer, :string, :int32, 1
     end
     add_enum "grpc.testing.PayloadType" do
       value :COMPRESSABLE, 0
@@ -99,6 +103,7 @@
     ReconnectInfo = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("grpc.testing.ReconnectInfo").msgclass
     LoadBalancerStatsRequest = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("grpc.testing.LoadBalancerStatsRequest").msgclass
     LoadBalancerStatsResponse = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("grpc.testing.LoadBalancerStatsResponse").msgclass
+    LoadBalancerStatsResponse::RpcsByPeer = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("grpc.testing.LoadBalancerStatsResponse.RpcsByPeer").msgclass
     PayloadType = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("grpc.testing.PayloadType").enummodule
     GrpclbRouteType = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("grpc.testing.GrpclbRouteType").enummodule
   end
diff --git a/src/ruby/pb/src/proto/grpc/testing/test_services_pb.rb b/src/ruby/pb/src/proto/grpc/testing/test_services_pb.rb
index 8138bd0..115acd0 100644
--- a/src/ruby/pb/src/proto/grpc/testing/test_services_pb.rb
+++ b/src/ruby/pb/src/proto/grpc/testing/test_services_pb.rb
@@ -36,31 +36,31 @@
         self.service_name = 'grpc.testing.TestService'
 
         # One empty request followed by one empty response.
-        rpc :EmptyCall, Empty, Empty
+        rpc :EmptyCall, ::Grpc::Testing::Empty, ::Grpc::Testing::Empty
         # One request followed by one response.
-        rpc :UnaryCall, SimpleRequest, SimpleResponse
+        rpc :UnaryCall, ::Grpc::Testing::SimpleRequest, ::Grpc::Testing::SimpleResponse
         # One request followed by one response. Response has cache control
         # headers set such that a caching HTTP proxy (such as GFE) can
         # satisfy subsequent requests.
-        rpc :CacheableUnaryCall, SimpleRequest, SimpleResponse
+        rpc :CacheableUnaryCall, ::Grpc::Testing::SimpleRequest, ::Grpc::Testing::SimpleResponse
         # One request followed by a sequence of responses (streamed download).
         # The server returns the payload with client desired type and sizes.
-        rpc :StreamingOutputCall, StreamingOutputCallRequest, stream(StreamingOutputCallResponse)
+        rpc :StreamingOutputCall, ::Grpc::Testing::StreamingOutputCallRequest, stream(::Grpc::Testing::StreamingOutputCallResponse)
         # A sequence of requests followed by one response (streamed upload).
         # The server returns the aggregated size of client payload as the result.
-        rpc :StreamingInputCall, stream(StreamingInputCallRequest), StreamingInputCallResponse
+        rpc :StreamingInputCall, stream(::Grpc::Testing::StreamingInputCallRequest), ::Grpc::Testing::StreamingInputCallResponse
         # A sequence of requests with each request served by the server immediately.
         # As one request could lead to multiple responses, this interface
         # demonstrates the idea of full duplexing.
-        rpc :FullDuplexCall, stream(StreamingOutputCallRequest), stream(StreamingOutputCallResponse)
+        rpc :FullDuplexCall, stream(::Grpc::Testing::StreamingOutputCallRequest), stream(::Grpc::Testing::StreamingOutputCallResponse)
         # A sequence of requests followed by a sequence of responses.
         # The server buffers all the client requests and then serves them in order. A
         # stream of responses are returned to the client when the server starts with
         # first request.
-        rpc :HalfDuplexCall, stream(StreamingOutputCallRequest), stream(StreamingOutputCallResponse)
+        rpc :HalfDuplexCall, stream(::Grpc::Testing::StreamingOutputCallRequest), stream(::Grpc::Testing::StreamingOutputCallResponse)
         # The test server will not implement this method. It will be used
         # to test the behavior when clients call unimplemented methods.
-        rpc :UnimplementedCall, Empty, Empty
+        rpc :UnimplementedCall, ::Grpc::Testing::Empty, ::Grpc::Testing::Empty
       end
 
       Stub = Service.rpc_stub_class
@@ -77,7 +77,7 @@
         self.service_name = 'grpc.testing.UnimplementedService'
 
         # A call that no server should implement
-        rpc :UnimplementedCall, Empty, Empty
+        rpc :UnimplementedCall, ::Grpc::Testing::Empty, ::Grpc::Testing::Empty
       end
 
       Stub = Service.rpc_stub_class
@@ -92,8 +92,8 @@
         self.unmarshal_class_method = :decode
         self.service_name = 'grpc.testing.ReconnectService'
 
-        rpc :Start, ReconnectParams, Empty
-        rpc :Stop, Empty, ReconnectInfo
+        rpc :Start, ::Grpc::Testing::ReconnectParams, ::Grpc::Testing::Empty
+        rpc :Stop, ::Grpc::Testing::Empty, ::Grpc::Testing::ReconnectInfo
       end
 
       Stub = Service.rpc_stub_class
@@ -109,7 +109,23 @@
         self.service_name = 'grpc.testing.LoadBalancerStatsService'
 
         # Gets the backend distribution for RPCs sent by a test client.
-        rpc :GetClientStats, LoadBalancerStatsRequest, LoadBalancerStatsResponse
+        rpc :GetClientStats, ::Grpc::Testing::LoadBalancerStatsRequest, ::Grpc::Testing::LoadBalancerStatsResponse
+      end
+
+      Stub = Service.rpc_stub_class
+    end
+    module XdsUpdateHealthService
+      # A service to remotely control health status of an xDS test server.
+      class Service
+
+        include GRPC::GenericService
+
+        self.marshal_class_method = :encode
+        self.unmarshal_class_method = :decode
+        self.service_name = 'grpc.testing.XdsUpdateHealthService'
+
+        rpc :SetServing, ::Grpc::Testing::Empty, ::Grpc::Testing::Empty
+        rpc :SetNotServing, ::Grpc::Testing::Empty, ::Grpc::Testing::Empty
       end
 
       Stub = Service.rpc_stub_class
diff --git a/src/ruby/qps/src/proto/grpc/testing/benchmark_service_services_pb.rb b/src/ruby/qps/src/proto/grpc/testing/benchmark_service_services_pb.rb
index 65e5a75..63e2d5d 100644
--- a/src/ruby/qps/src/proto/grpc/testing/benchmark_service_services_pb.rb
+++ b/src/ruby/qps/src/proto/grpc/testing/benchmark_service_services_pb.rb
@@ -34,20 +34,20 @@
 
         # One request followed by one response.
         # The server returns the client payload as-is.
-        rpc :UnaryCall, SimpleRequest, SimpleResponse
+        rpc :UnaryCall, ::Grpc::Testing::SimpleRequest, ::Grpc::Testing::SimpleResponse
         # Repeated sequence of one request followed by one response.
         # Should be called streaming ping-pong
         # The server returns the client payload as-is on each response
-        rpc :StreamingCall, stream(SimpleRequest), stream(SimpleResponse)
+        rpc :StreamingCall, stream(::Grpc::Testing::SimpleRequest), stream(::Grpc::Testing::SimpleResponse)
         # Single-sided unbounded streaming from client to server
         # The server returns the client payload as-is once the client does WritesDone
-        rpc :StreamingFromClient, stream(SimpleRequest), SimpleResponse
+        rpc :StreamingFromClient, stream(::Grpc::Testing::SimpleRequest), ::Grpc::Testing::SimpleResponse
         # Single-sided unbounded streaming from server to client
         # The server repeatedly returns the client payload as-is
-        rpc :StreamingFromServer, SimpleRequest, stream(SimpleResponse)
+        rpc :StreamingFromServer, ::Grpc::Testing::SimpleRequest, stream(::Grpc::Testing::SimpleResponse)
         # Two-sided unbounded streaming between server to client
         # Both sides send the content of their own choice to the other
-        rpc :StreamingBothWays, stream(SimpleRequest), stream(SimpleResponse)
+        rpc :StreamingBothWays, stream(::Grpc::Testing::SimpleRequest), stream(::Grpc::Testing::SimpleResponse)
       end
 
       Stub = Service.rpc_stub_class
diff --git a/src/ruby/qps/src/proto/grpc/testing/messages_pb.rb b/src/ruby/qps/src/proto/grpc/testing/messages_pb.rb
index f492ccf..d902ae0 100644
--- a/src/ruby/qps/src/proto/grpc/testing/messages_pb.rb
+++ b/src/ruby/qps/src/proto/grpc/testing/messages_pb.rb
@@ -71,6 +71,10 @@
     add_message "grpc.testing.LoadBalancerStatsResponse" do
       map :rpcs_by_peer, :string, :int32, 1
       optional :num_failures, :int32, 2
+      map :rpcs_by_method, :string, :message, 3, "grpc.testing.LoadBalancerStatsResponse.RpcsByPeer"
+    end
+    add_message "grpc.testing.LoadBalancerStatsResponse.RpcsByPeer" do
+      map :rpcs_by_peer, :string, :int32, 1
     end
     add_enum "grpc.testing.PayloadType" do
       value :COMPRESSABLE, 0
@@ -99,6 +103,7 @@
     ReconnectInfo = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("grpc.testing.ReconnectInfo").msgclass
     LoadBalancerStatsRequest = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("grpc.testing.LoadBalancerStatsRequest").msgclass
     LoadBalancerStatsResponse = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("grpc.testing.LoadBalancerStatsResponse").msgclass
+    LoadBalancerStatsResponse::RpcsByPeer = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("grpc.testing.LoadBalancerStatsResponse.RpcsByPeer").msgclass
     PayloadType = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("grpc.testing.PayloadType").enummodule
     GrpclbRouteType = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("grpc.testing.GrpclbRouteType").enummodule
   end
diff --git a/src/ruby/qps/src/proto/grpc/testing/report_qps_scenario_service_services_pb.rb b/src/ruby/qps/src/proto/grpc/testing/report_qps_scenario_service_services_pb.rb
index ddc81be..5e41cfe 100644
--- a/src/ruby/qps/src/proto/grpc/testing/report_qps_scenario_service_services_pb.rb
+++ b/src/ruby/qps/src/proto/grpc/testing/report_qps_scenario_service_services_pb.rb
@@ -33,7 +33,7 @@
         self.service_name = 'grpc.testing.ReportQpsScenarioService'
 
         # Report results of a QPS test benchmark scenario.
-        rpc :ReportScenario, ScenarioResult, Void
+        rpc :ReportScenario, ::Grpc::Testing::ScenarioResult, ::Grpc::Testing::Void
       end
 
       Stub = Service.rpc_stub_class
diff --git a/src/ruby/qps/src/proto/grpc/testing/worker_service_services_pb.rb b/src/ruby/qps/src/proto/grpc/testing/worker_service_services_pb.rb
index a7ecc95..049db47 100644
--- a/src/ruby/qps/src/proto/grpc/testing/worker_service_services_pb.rb
+++ b/src/ruby/qps/src/proto/grpc/testing/worker_service_services_pb.rb
@@ -38,18 +38,18 @@
         # stats. Closing the stream will initiate shutdown of the test server
         # and once the shutdown has finished, the OK status is sent to terminate
         # this RPC.
-        rpc :RunServer, stream(ServerArgs), stream(ServerStatus)
+        rpc :RunServer, stream(::Grpc::Testing::ServerArgs), stream(::Grpc::Testing::ServerStatus)
         # Start client with specified workload.
         # First request sent specifies the ClientConfig followed by ClientStatus
         # response. After that, a "Mark" can be sent anytime to request the latest
         # stats. Closing the stream will initiate shutdown of the test client
         # and once the shutdown has finished, the OK status is sent to terminate
         # this RPC.
-        rpc :RunClient, stream(ClientArgs), stream(ClientStatus)
+        rpc :RunClient, stream(::Grpc::Testing::ClientArgs), stream(::Grpc::Testing::ClientStatus)
         # Just return the core count - unary call
-        rpc :CoreCount, CoreRequest, CoreResponse
+        rpc :CoreCount, ::Grpc::Testing::CoreRequest, ::Grpc::Testing::CoreResponse
         # Quit this worker
-        rpc :QuitWorker, Void, Void
+        rpc :QuitWorker, ::Grpc::Testing::Void, ::Grpc::Testing::Void
       end
 
       Stub = Service.rpc_stub_class
diff --git a/src/ruby/spec/pb/codegen/grpc/testing/same_package_service_name.proto b/src/ruby/spec/pb/codegen/grpc/testing/same_package_service_name.proto
new file mode 100644
index 0000000..15dd8f0
--- /dev/null
+++ b/src/ruby/spec/pb/codegen/grpc/testing/same_package_service_name.proto
@@ -0,0 +1,27 @@
+// Copyright 2020 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.
+
+syntax = "proto3";
+
+package same_name;
+
+service SameName {
+  rpc Health(Request) returns (Status);
+}
+
+message Status {
+  string msg = 1;
+}
+
+message Request {}
diff --git a/src/ruby/spec/pb/codegen/grpc/testing/same_ruby_package_service_name.proto b/src/ruby/spec/pb/codegen/grpc/testing/same_ruby_package_service_name.proto
new file mode 100644
index 0000000..9ce631c
--- /dev/null
+++ b/src/ruby/spec/pb/codegen/grpc/testing/same_ruby_package_service_name.proto
@@ -0,0 +1,29 @@
+// Copyright 2020 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.
+
+syntax = "proto3";
+
+package other_name;
+
+option ruby_package = "SameName2";
+
+service SameName2 {
+  rpc Health(Request) returns (Status);
+}
+
+message Status {
+  string msg = 1;
+}
+
+message Request {}
diff --git a/src/ruby/spec/pb/codegen/package_option_spec.rb b/src/ruby/spec/pb/codegen/package_option_spec.rb
index a4668ac..f64b4f6 100644
--- a/src/ruby/spec/pb/codegen/package_option_spec.rb
+++ b/src/ruby/spec/pb/codegen/package_option_spec.rb
@@ -48,6 +48,26 @@
       expect(services[:NestedMessageTest].output).to eq(RPC::Test::New::Package::Options::Bar::Baz)
     end
   end
+
+  it 'should generate when package and service has same name' do
+    with_protos(['grpc/testing/same_package_service_name.proto']) do
+      expect { SameName::SameName::Service }.to raise_error(NameError)
+      expect(require('grpc/testing/same_package_service_name_services_pb')).to be_truthy
+      expect { SameName::SameName::Service }.to_not raise_error
+      expect { SameName::Request }.to_not raise_error
+      expect { SameName::Status }.to_not raise_error
+    end
+  end
+
+  it 'should generate when ruby_package and service has same name' do
+    with_protos(['grpc/testing/same_ruby_package_service_name.proto']) do
+      expect { SameName2::SameName2::Service }.to raise_error(NameError)
+      expect(require('grpc/testing/same_ruby_package_service_name_services_pb')).to be_truthy
+      expect { SameName2::SameName2::Service }.to_not raise_error
+      expect { SameName2::Request }.to_not raise_error
+      expect { SameName2::Status }.to_not raise_error
+    end
+  end
 end
 
 def with_protos(file_paths)
diff --git a/src/ruby/spec/user_agent_spec.rb b/src/ruby/spec/user_agent_spec.rb
new file mode 100644
index 0000000..a5aa206
--- /dev/null
+++ b/src/ruby/spec/user_agent_spec.rb
@@ -0,0 +1,74 @@
+# Copyright 2020 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.
+
+require 'spec_helper'
+
+# a test service that checks the cert of its peer
+class UserAgentEchoService
+  include GRPC::GenericService
+  rpc :an_rpc, EchoMsg, EchoMsg
+
+  def an_rpc(_req, call)
+    EchoMsg.new(msg: call.metadata['user-agent'])
+  end
+end
+
+UserAgentEchoServiceStub = UserAgentEchoService.rpc_stub_class
+
+describe 'user agent' do
+  RpcServer = GRPC::RpcServer
+
+  before(:all) do
+    server_opts = {
+      poll_period: 1
+    }
+    @srv = new_rpc_server_for_testing(**server_opts)
+    @port = @srv.add_http2_port('0.0.0.0:0', :this_port_is_insecure)
+    @srv.handle(UserAgentEchoService)
+    @srv_thd = Thread.new { @srv.run }
+    @srv.wait_till_running
+  end
+
+  after(:all) do
+    expect(@srv.stopped?).to be(false)
+    @srv.stop
+    @srv_thd.join
+  end
+
+  it 'client sends expected user agent' do
+    stub = UserAgentEchoServiceStub.new("localhost:#{@port}",
+                                        :this_channel_is_insecure,
+                                        {})
+    response = stub.an_rpc(EchoMsg.new)
+    expected_user_agent_prefix = "grpc-ruby/#{GRPC::VERSION}"
+    expect(response.msg.start_with?(expected_user_agent_prefix)).to be true
+    # check that the expected user agent prefix occurs in the real user agent exactly once
+    expect(response.msg.split(expected_user_agent_prefix).size).to eq 2
+  end
+
+  it 'user agent header does not grow when the same channel args hash is used across multiple stubs' do
+    shared_channel_args_hash = {}
+    10.times do
+      stub = UserAgentEchoServiceStub.new("localhost:#{@port}",
+                                          :this_channel_is_insecure,
+                                          channel_args: shared_channel_args_hash)
+      response = stub.an_rpc(EchoMsg.new)
+      puts "got echo response: #{response.msg}"
+      expected_user_agent_prefix = "grpc-ruby/#{GRPC::VERSION}"
+      expect(response.msg.start_with?(expected_user_agent_prefix)).to be true
+      # check that the expected user agent prefix occurs in the real user agent exactly once
+      expect(response.msg.split(expected_user_agent_prefix).size).to eq 2
+    end
+  end
+end