diff --git a/BUILD b/BUILD index f0de4399beb..a566057e926 100644 --- a/BUILD +++ b/BUILD @@ -1070,10 +1070,10 @@ grpc_cc_library( "src/core/ext/filters/client_channel/parse_address.cc", "src/core/ext/filters/client_channel/proxy_mapper.cc", "src/core/ext/filters/client_channel/proxy_mapper_registry.cc", + "src/core/ext/filters/client_channel/request_routing.cc", "src/core/ext/filters/client_channel/resolver.cc", "src/core/ext/filters/client_channel/resolver_registry.cc", "src/core/ext/filters/client_channel/resolver_result_parsing.cc", - "src/core/ext/filters/client_channel/resolving_lb_policy.cc", "src/core/ext/filters/client_channel/retry_throttle.cc", "src/core/ext/filters/client_channel/server_address.cc", "src/core/ext/filters/client_channel/subchannel.cc", @@ -1096,11 +1096,11 @@ grpc_cc_library( "src/core/ext/filters/client_channel/parse_address.h", "src/core/ext/filters/client_channel/proxy_mapper.h", "src/core/ext/filters/client_channel/proxy_mapper_registry.h", + "src/core/ext/filters/client_channel/request_routing.h", "src/core/ext/filters/client_channel/resolver.h", "src/core/ext/filters/client_channel/resolver_factory.h", "src/core/ext/filters/client_channel/resolver_registry.h", "src/core/ext/filters/client_channel/resolver_result_parsing.h", - "src/core/ext/filters/client_channel/resolving_lb_policy.h", "src/core/ext/filters/client_channel/retry_throttle.h", "src/core/ext/filters/client_channel/server_address.h", "src/core/ext/filters/client_channel/subchannel.h", diff --git a/CMakeLists.txt b/CMakeLists.txt index 458e9b88b74..f494ef0094c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1232,10 +1232,10 @@ add_library(grpc src/core/ext/filters/client_channel/parse_address.cc src/core/ext/filters/client_channel/proxy_mapper.cc src/core/ext/filters/client_channel/proxy_mapper_registry.cc + src/core/ext/filters/client_channel/request_routing.cc src/core/ext/filters/client_channel/resolver.cc src/core/ext/filters/client_channel/resolver_registry.cc src/core/ext/filters/client_channel/resolver_result_parsing.cc - src/core/ext/filters/client_channel/resolving_lb_policy.cc src/core/ext/filters/client_channel/retry_throttle.cc src/core/ext/filters/client_channel/server_address.cc src/core/ext/filters/client_channel/subchannel.cc @@ -1587,10 +1587,10 @@ add_library(grpc_cronet src/core/ext/filters/client_channel/parse_address.cc src/core/ext/filters/client_channel/proxy_mapper.cc src/core/ext/filters/client_channel/proxy_mapper_registry.cc + src/core/ext/filters/client_channel/request_routing.cc src/core/ext/filters/client_channel/resolver.cc src/core/ext/filters/client_channel/resolver_registry.cc src/core/ext/filters/client_channel/resolver_result_parsing.cc - src/core/ext/filters/client_channel/resolving_lb_policy.cc src/core/ext/filters/client_channel/retry_throttle.cc src/core/ext/filters/client_channel/server_address.cc src/core/ext/filters/client_channel/subchannel.cc @@ -1965,10 +1965,10 @@ add_library(grpc_test_util src/core/ext/filters/client_channel/parse_address.cc src/core/ext/filters/client_channel/proxy_mapper.cc src/core/ext/filters/client_channel/proxy_mapper_registry.cc + src/core/ext/filters/client_channel/request_routing.cc src/core/ext/filters/client_channel/resolver.cc src/core/ext/filters/client_channel/resolver_registry.cc src/core/ext/filters/client_channel/resolver_result_parsing.cc - src/core/ext/filters/client_channel/resolving_lb_policy.cc src/core/ext/filters/client_channel/retry_throttle.cc src/core/ext/filters/client_channel/server_address.cc src/core/ext/filters/client_channel/subchannel.cc @@ -2290,10 +2290,10 @@ add_library(grpc_test_util_unsecure src/core/ext/filters/client_channel/parse_address.cc src/core/ext/filters/client_channel/proxy_mapper.cc src/core/ext/filters/client_channel/proxy_mapper_registry.cc + src/core/ext/filters/client_channel/request_routing.cc src/core/ext/filters/client_channel/resolver.cc src/core/ext/filters/client_channel/resolver_registry.cc src/core/ext/filters/client_channel/resolver_result_parsing.cc - src/core/ext/filters/client_channel/resolving_lb_policy.cc src/core/ext/filters/client_channel/retry_throttle.cc src/core/ext/filters/client_channel/server_address.cc src/core/ext/filters/client_channel/subchannel.cc @@ -2626,10 +2626,10 @@ add_library(grpc_unsecure src/core/ext/filters/client_channel/parse_address.cc src/core/ext/filters/client_channel/proxy_mapper.cc src/core/ext/filters/client_channel/proxy_mapper_registry.cc + src/core/ext/filters/client_channel/request_routing.cc src/core/ext/filters/client_channel/resolver.cc src/core/ext/filters/client_channel/resolver_registry.cc src/core/ext/filters/client_channel/resolver_result_parsing.cc - src/core/ext/filters/client_channel/resolving_lb_policy.cc src/core/ext/filters/client_channel/retry_throttle.cc src/core/ext/filters/client_channel/server_address.cc src/core/ext/filters/client_channel/subchannel.cc @@ -3483,10 +3483,10 @@ add_library(grpc++_cronet src/core/ext/filters/client_channel/parse_address.cc src/core/ext/filters/client_channel/proxy_mapper.cc src/core/ext/filters/client_channel/proxy_mapper_registry.cc + src/core/ext/filters/client_channel/request_routing.cc src/core/ext/filters/client_channel/resolver.cc src/core/ext/filters/client_channel/resolver_registry.cc src/core/ext/filters/client_channel/resolver_result_parsing.cc - src/core/ext/filters/client_channel/resolving_lb_policy.cc src/core/ext/filters/client_channel/retry_throttle.cc src/core/ext/filters/client_channel/server_address.cc src/core/ext/filters/client_channel/subchannel.cc diff --git a/Makefile b/Makefile index 9d0b37b687a..7cfe37384aa 100644 --- a/Makefile +++ b/Makefile @@ -3758,10 +3758,10 @@ LIBGRPC_SRC = \ src/core/ext/filters/client_channel/parse_address.cc \ src/core/ext/filters/client_channel/proxy_mapper.cc \ src/core/ext/filters/client_channel/proxy_mapper_registry.cc \ + src/core/ext/filters/client_channel/request_routing.cc \ src/core/ext/filters/client_channel/resolver.cc \ src/core/ext/filters/client_channel/resolver_registry.cc \ src/core/ext/filters/client_channel/resolver_result_parsing.cc \ - src/core/ext/filters/client_channel/resolving_lb_policy.cc \ src/core/ext/filters/client_channel/retry_throttle.cc \ src/core/ext/filters/client_channel/server_address.cc \ src/core/ext/filters/client_channel/subchannel.cc \ @@ -4107,10 +4107,10 @@ LIBGRPC_CRONET_SRC = \ src/core/ext/filters/client_channel/parse_address.cc \ src/core/ext/filters/client_channel/proxy_mapper.cc \ src/core/ext/filters/client_channel/proxy_mapper_registry.cc \ + src/core/ext/filters/client_channel/request_routing.cc \ src/core/ext/filters/client_channel/resolver.cc \ src/core/ext/filters/client_channel/resolver_registry.cc \ src/core/ext/filters/client_channel/resolver_result_parsing.cc \ - src/core/ext/filters/client_channel/resolving_lb_policy.cc \ src/core/ext/filters/client_channel/retry_throttle.cc \ src/core/ext/filters/client_channel/server_address.cc \ src/core/ext/filters/client_channel/subchannel.cc \ @@ -4478,10 +4478,10 @@ LIBGRPC_TEST_UTIL_SRC = \ src/core/ext/filters/client_channel/parse_address.cc \ src/core/ext/filters/client_channel/proxy_mapper.cc \ src/core/ext/filters/client_channel/proxy_mapper_registry.cc \ + src/core/ext/filters/client_channel/request_routing.cc \ src/core/ext/filters/client_channel/resolver.cc \ src/core/ext/filters/client_channel/resolver_registry.cc \ src/core/ext/filters/client_channel/resolver_result_parsing.cc \ - src/core/ext/filters/client_channel/resolving_lb_policy.cc \ src/core/ext/filters/client_channel/retry_throttle.cc \ src/core/ext/filters/client_channel/server_address.cc \ src/core/ext/filters/client_channel/subchannel.cc \ @@ -4790,10 +4790,10 @@ LIBGRPC_TEST_UTIL_UNSECURE_SRC = \ src/core/ext/filters/client_channel/parse_address.cc \ src/core/ext/filters/client_channel/proxy_mapper.cc \ src/core/ext/filters/client_channel/proxy_mapper_registry.cc \ + src/core/ext/filters/client_channel/request_routing.cc \ src/core/ext/filters/client_channel/resolver.cc \ src/core/ext/filters/client_channel/resolver_registry.cc \ src/core/ext/filters/client_channel/resolver_result_parsing.cc \ - src/core/ext/filters/client_channel/resolving_lb_policy.cc \ src/core/ext/filters/client_channel/retry_throttle.cc \ src/core/ext/filters/client_channel/server_address.cc \ src/core/ext/filters/client_channel/subchannel.cc \ @@ -5100,10 +5100,10 @@ LIBGRPC_UNSECURE_SRC = \ src/core/ext/filters/client_channel/parse_address.cc \ src/core/ext/filters/client_channel/proxy_mapper.cc \ src/core/ext/filters/client_channel/proxy_mapper_registry.cc \ + src/core/ext/filters/client_channel/request_routing.cc \ src/core/ext/filters/client_channel/resolver.cc \ src/core/ext/filters/client_channel/resolver_registry.cc \ src/core/ext/filters/client_channel/resolver_result_parsing.cc \ - src/core/ext/filters/client_channel/resolving_lb_policy.cc \ src/core/ext/filters/client_channel/retry_throttle.cc \ src/core/ext/filters/client_channel/server_address.cc \ src/core/ext/filters/client_channel/subchannel.cc \ @@ -5934,10 +5934,10 @@ LIBGRPC++_CRONET_SRC = \ src/core/ext/filters/client_channel/parse_address.cc \ src/core/ext/filters/client_channel/proxy_mapper.cc \ src/core/ext/filters/client_channel/proxy_mapper_registry.cc \ + src/core/ext/filters/client_channel/request_routing.cc \ src/core/ext/filters/client_channel/resolver.cc \ src/core/ext/filters/client_channel/resolver_registry.cc \ src/core/ext/filters/client_channel/resolver_result_parsing.cc \ - src/core/ext/filters/client_channel/resolving_lb_policy.cc \ src/core/ext/filters/client_channel/retry_throttle.cc \ src/core/ext/filters/client_channel/server_address.cc \ src/core/ext/filters/client_channel/subchannel.cc \ diff --git a/build.yaml b/build.yaml index d347bcd8189..77ad81ddda2 100644 --- a/build.yaml +++ b/build.yaml @@ -587,11 +587,11 @@ filegroups: - src/core/ext/filters/client_channel/parse_address.h - src/core/ext/filters/client_channel/proxy_mapper.h - src/core/ext/filters/client_channel/proxy_mapper_registry.h + - src/core/ext/filters/client_channel/request_routing.h - src/core/ext/filters/client_channel/resolver.h - src/core/ext/filters/client_channel/resolver_factory.h - src/core/ext/filters/client_channel/resolver_registry.h - src/core/ext/filters/client_channel/resolver_result_parsing.h - - src/core/ext/filters/client_channel/resolving_lb_policy.h - src/core/ext/filters/client_channel/retry_throttle.h - src/core/ext/filters/client_channel/server_address.h - src/core/ext/filters/client_channel/subchannel.h @@ -614,10 +614,10 @@ filegroups: - src/core/ext/filters/client_channel/parse_address.cc - src/core/ext/filters/client_channel/proxy_mapper.cc - src/core/ext/filters/client_channel/proxy_mapper_registry.cc + - src/core/ext/filters/client_channel/request_routing.cc - src/core/ext/filters/client_channel/resolver.cc - src/core/ext/filters/client_channel/resolver_registry.cc - src/core/ext/filters/client_channel/resolver_result_parsing.cc - - src/core/ext/filters/client_channel/resolving_lb_policy.cc - src/core/ext/filters/client_channel/retry_throttle.cc - src/core/ext/filters/client_channel/server_address.cc - src/core/ext/filters/client_channel/subchannel.cc diff --git a/config.m4 b/config.m4 index 2616803d9b0..5746caf694a 100644 --- a/config.m4 +++ b/config.m4 @@ -355,10 +355,10 @@ if test "$PHP_GRPC" != "no"; then src/core/ext/filters/client_channel/parse_address.cc \ src/core/ext/filters/client_channel/proxy_mapper.cc \ src/core/ext/filters/client_channel/proxy_mapper_registry.cc \ + src/core/ext/filters/client_channel/request_routing.cc \ src/core/ext/filters/client_channel/resolver.cc \ src/core/ext/filters/client_channel/resolver_registry.cc \ src/core/ext/filters/client_channel/resolver_result_parsing.cc \ - src/core/ext/filters/client_channel/resolving_lb_policy.cc \ src/core/ext/filters/client_channel/retry_throttle.cc \ src/core/ext/filters/client_channel/server_address.cc \ src/core/ext/filters/client_channel/subchannel.cc \ diff --git a/config.w32 b/config.w32 index 64eca2a8472..5659d8b8408 100644 --- a/config.w32 +++ b/config.w32 @@ -330,10 +330,10 @@ if (PHP_GRPC != "no") { "src\\core\\ext\\filters\\client_channel\\parse_address.cc " + "src\\core\\ext\\filters\\client_channel\\proxy_mapper.cc " + "src\\core\\ext\\filters\\client_channel\\proxy_mapper_registry.cc " + + "src\\core\\ext\\filters\\client_channel\\request_routing.cc " + "src\\core\\ext\\filters\\client_channel\\resolver.cc " + "src\\core\\ext\\filters\\client_channel\\resolver_registry.cc " + "src\\core\\ext\\filters\\client_channel\\resolver_result_parsing.cc " + - "src\\core\\ext\\filters\\client_channel\\resolving_lb_policy.cc " + "src\\core\\ext\\filters\\client_channel\\retry_throttle.cc " + "src\\core\\ext\\filters\\client_channel\\server_address.cc " + "src\\core\\ext\\filters\\client_channel\\subchannel.cc " + diff --git a/gRPC-C++.podspec b/gRPC-C++.podspec index 272e41f8223..15ce090bd9b 100644 --- a/gRPC-C++.podspec +++ b/gRPC-C++.podspec @@ -360,11 +360,11 @@ Pod::Spec.new do |s| 'src/core/ext/filters/client_channel/parse_address.h', 'src/core/ext/filters/client_channel/proxy_mapper.h', 'src/core/ext/filters/client_channel/proxy_mapper_registry.h', + 'src/core/ext/filters/client_channel/request_routing.h', 'src/core/ext/filters/client_channel/resolver.h', 'src/core/ext/filters/client_channel/resolver_factory.h', 'src/core/ext/filters/client_channel/resolver_registry.h', 'src/core/ext/filters/client_channel/resolver_result_parsing.h', - 'src/core/ext/filters/client_channel/resolving_lb_policy.h', 'src/core/ext/filters/client_channel/retry_throttle.h', 'src/core/ext/filters/client_channel/server_address.h', 'src/core/ext/filters/client_channel/subchannel.h', diff --git a/gRPC-Core.podspec b/gRPC-Core.podspec index 61409e9c133..92626f3e84b 100644 --- a/gRPC-Core.podspec +++ b/gRPC-Core.podspec @@ -354,11 +354,11 @@ Pod::Spec.new do |s| 'src/core/ext/filters/client_channel/parse_address.h', 'src/core/ext/filters/client_channel/proxy_mapper.h', 'src/core/ext/filters/client_channel/proxy_mapper_registry.h', + 'src/core/ext/filters/client_channel/request_routing.h', 'src/core/ext/filters/client_channel/resolver.h', 'src/core/ext/filters/client_channel/resolver_factory.h', 'src/core/ext/filters/client_channel/resolver_registry.h', 'src/core/ext/filters/client_channel/resolver_result_parsing.h', - 'src/core/ext/filters/client_channel/resolving_lb_policy.h', 'src/core/ext/filters/client_channel/retry_throttle.h', 'src/core/ext/filters/client_channel/server_address.h', 'src/core/ext/filters/client_channel/subchannel.h', @@ -801,10 +801,10 @@ Pod::Spec.new do |s| 'src/core/ext/filters/client_channel/parse_address.cc', 'src/core/ext/filters/client_channel/proxy_mapper.cc', 'src/core/ext/filters/client_channel/proxy_mapper_registry.cc', + 'src/core/ext/filters/client_channel/request_routing.cc', 'src/core/ext/filters/client_channel/resolver.cc', 'src/core/ext/filters/client_channel/resolver_registry.cc', 'src/core/ext/filters/client_channel/resolver_result_parsing.cc', - 'src/core/ext/filters/client_channel/resolving_lb_policy.cc', 'src/core/ext/filters/client_channel/retry_throttle.cc', 'src/core/ext/filters/client_channel/server_address.cc', 'src/core/ext/filters/client_channel/subchannel.cc', @@ -984,11 +984,11 @@ Pod::Spec.new do |s| 'src/core/ext/filters/client_channel/parse_address.h', 'src/core/ext/filters/client_channel/proxy_mapper.h', 'src/core/ext/filters/client_channel/proxy_mapper_registry.h', + 'src/core/ext/filters/client_channel/request_routing.h', 'src/core/ext/filters/client_channel/resolver.h', 'src/core/ext/filters/client_channel/resolver_factory.h', 'src/core/ext/filters/client_channel/resolver_registry.h', 'src/core/ext/filters/client_channel/resolver_result_parsing.h', - 'src/core/ext/filters/client_channel/resolving_lb_policy.h', 'src/core/ext/filters/client_channel/retry_throttle.h', 'src/core/ext/filters/client_channel/server_address.h', 'src/core/ext/filters/client_channel/subchannel.h', diff --git a/grpc.gemspec b/grpc.gemspec index 0ab718a0668..a4e25d7bb22 100644 --- a/grpc.gemspec +++ b/grpc.gemspec @@ -288,11 +288,11 @@ Gem::Specification.new do |s| s.files += %w( src/core/ext/filters/client_channel/parse_address.h ) s.files += %w( src/core/ext/filters/client_channel/proxy_mapper.h ) s.files += %w( src/core/ext/filters/client_channel/proxy_mapper_registry.h ) + s.files += %w( src/core/ext/filters/client_channel/request_routing.h ) s.files += %w( src/core/ext/filters/client_channel/resolver.h ) s.files += %w( src/core/ext/filters/client_channel/resolver_factory.h ) s.files += %w( src/core/ext/filters/client_channel/resolver_registry.h ) s.files += %w( src/core/ext/filters/client_channel/resolver_result_parsing.h ) - s.files += %w( src/core/ext/filters/client_channel/resolving_lb_policy.h ) s.files += %w( src/core/ext/filters/client_channel/retry_throttle.h ) s.files += %w( src/core/ext/filters/client_channel/server_address.h ) s.files += %w( src/core/ext/filters/client_channel/subchannel.h ) @@ -738,10 +738,10 @@ Gem::Specification.new do |s| s.files += %w( src/core/ext/filters/client_channel/parse_address.cc ) s.files += %w( src/core/ext/filters/client_channel/proxy_mapper.cc ) s.files += %w( src/core/ext/filters/client_channel/proxy_mapper_registry.cc ) + s.files += %w( src/core/ext/filters/client_channel/request_routing.cc ) s.files += %w( src/core/ext/filters/client_channel/resolver.cc ) s.files += %w( src/core/ext/filters/client_channel/resolver_registry.cc ) s.files += %w( src/core/ext/filters/client_channel/resolver_result_parsing.cc ) - s.files += %w( src/core/ext/filters/client_channel/resolving_lb_policy.cc ) s.files += %w( src/core/ext/filters/client_channel/retry_throttle.cc ) s.files += %w( src/core/ext/filters/client_channel/server_address.cc ) s.files += %w( src/core/ext/filters/client_channel/subchannel.cc ) diff --git a/grpc.gyp b/grpc.gyp index ca9d017dbbe..113c17f0d09 100644 --- a/grpc.gyp +++ b/grpc.gyp @@ -537,10 +537,10 @@ 'src/core/ext/filters/client_channel/parse_address.cc', 'src/core/ext/filters/client_channel/proxy_mapper.cc', 'src/core/ext/filters/client_channel/proxy_mapper_registry.cc', + 'src/core/ext/filters/client_channel/request_routing.cc', 'src/core/ext/filters/client_channel/resolver.cc', 'src/core/ext/filters/client_channel/resolver_registry.cc', 'src/core/ext/filters/client_channel/resolver_result_parsing.cc', - 'src/core/ext/filters/client_channel/resolving_lb_policy.cc', 'src/core/ext/filters/client_channel/retry_throttle.cc', 'src/core/ext/filters/client_channel/server_address.cc', 'src/core/ext/filters/client_channel/subchannel.cc', @@ -801,10 +801,10 @@ 'src/core/ext/filters/client_channel/parse_address.cc', 'src/core/ext/filters/client_channel/proxy_mapper.cc', 'src/core/ext/filters/client_channel/proxy_mapper_registry.cc', + 'src/core/ext/filters/client_channel/request_routing.cc', 'src/core/ext/filters/client_channel/resolver.cc', 'src/core/ext/filters/client_channel/resolver_registry.cc', 'src/core/ext/filters/client_channel/resolver_result_parsing.cc', - 'src/core/ext/filters/client_channel/resolving_lb_policy.cc', 'src/core/ext/filters/client_channel/retry_throttle.cc', 'src/core/ext/filters/client_channel/server_address.cc', 'src/core/ext/filters/client_channel/subchannel.cc', @@ -1046,10 +1046,10 @@ 'src/core/ext/filters/client_channel/parse_address.cc', 'src/core/ext/filters/client_channel/proxy_mapper.cc', 'src/core/ext/filters/client_channel/proxy_mapper_registry.cc', + 'src/core/ext/filters/client_channel/request_routing.cc', 'src/core/ext/filters/client_channel/resolver.cc', 'src/core/ext/filters/client_channel/resolver_registry.cc', 'src/core/ext/filters/client_channel/resolver_result_parsing.cc', - 'src/core/ext/filters/client_channel/resolving_lb_policy.cc', 'src/core/ext/filters/client_channel/retry_throttle.cc', 'src/core/ext/filters/client_channel/server_address.cc', 'src/core/ext/filters/client_channel/subchannel.cc', @@ -1302,10 +1302,10 @@ 'src/core/ext/filters/client_channel/parse_address.cc', 'src/core/ext/filters/client_channel/proxy_mapper.cc', 'src/core/ext/filters/client_channel/proxy_mapper_registry.cc', + 'src/core/ext/filters/client_channel/request_routing.cc', 'src/core/ext/filters/client_channel/resolver.cc', 'src/core/ext/filters/client_channel/resolver_registry.cc', 'src/core/ext/filters/client_channel/resolver_result_parsing.cc', - 'src/core/ext/filters/client_channel/resolving_lb_policy.cc', 'src/core/ext/filters/client_channel/retry_throttle.cc', 'src/core/ext/filters/client_channel/server_address.cc', 'src/core/ext/filters/client_channel/subchannel.cc', diff --git a/package.xml b/package.xml index e6b793fd1d1..7a1d26c47c5 100644 --- a/package.xml +++ b/package.xml @@ -293,11 +293,11 @@ + - @@ -743,10 +743,10 @@ + - diff --git a/src/core/ext/filters/client_channel/client_channel.cc b/src/core/ext/filters/client_channel/client_channel.cc index 6de27369ea4..38525dbf97e 100644 --- a/src/core/ext/filters/client_channel/client_channel.cc +++ b/src/core/ext/filters/client_channel/client_channel.cc @@ -32,14 +32,12 @@ #include #include "src/core/ext/filters/client_channel/backup_poller.h" -#include "src/core/ext/filters/client_channel/global_subchannel_pool.h" #include "src/core/ext/filters/client_channel/http_connect_handshaker.h" #include "src/core/ext/filters/client_channel/lb_policy_registry.h" -#include "src/core/ext/filters/client_channel/local_subchannel_pool.h" #include "src/core/ext/filters/client_channel/proxy_mapper_registry.h" +#include "src/core/ext/filters/client_channel/request_routing.h" #include "src/core/ext/filters/client_channel/resolver_registry.h" #include "src/core/ext/filters/client_channel/resolver_result_parsing.h" -#include "src/core/ext/filters/client_channel/resolving_lb_policy.h" #include "src/core/ext/filters/client_channel/retry_throttle.h" #include "src/core/ext/filters/client_channel/subchannel.h" #include "src/core/ext/filters/deadline/deadline_filter.h" @@ -70,8 +68,6 @@ using grpc_core::internal::ClientChannelMethodParamsTable; using grpc_core::internal::ProcessedResolverResult; using grpc_core::internal::ServerRetryThrottleData; -using grpc_core::LoadBalancingPolicy; - /* Client channel implementation */ // By default, we buffer 256 KiB per RPC for retries. @@ -90,171 +86,44 @@ grpc_core::TraceFlag grpc_client_channel_trace(false, "client_channel"); struct external_connectivity_watcher; -struct QueuedPick { - LoadBalancingPolicy::PickState pick; - grpc_call_element* elem; - QueuedPick* next = nullptr; -}; - typedef struct client_channel_channel_data { + grpc_core::ManualConstructor request_router; + bool deadline_checking_enabled; bool enable_retries; size_t per_rpc_retry_buffer_size; /** combiner protecting all variables below in this data structure */ grpc_combiner* combiner; + /** retry throttle data */ + grpc_core::RefCountedPtr retry_throttle_data; + /** maps method names to method_parameters structs */ + grpc_core::RefCountedPtr method_params_table; /** owning stack */ grpc_channel_stack* owning_stack; /** interested parties (owned) */ grpc_pollset_set* interested_parties; - // Client channel factory. Holds a ref. - grpc_client_channel_factory* client_channel_factory; - // Subchannel pool. - grpc_core::RefCountedPtr subchannel_pool; - - grpc_core::channelz::ClientChannelNode* channelz_node; - - // Resolving LB policy. - grpc_core::OrphanablePtr resolving_lb_policy; - // Subchannel picker from LB policy. - grpc_core::UniquePtr picker; - // Linked list of queued picks. - QueuedPick* queued_picks; - - bool have_service_config; - /** retry throttle data from service config */ - grpc_core::RefCountedPtr retry_throttle_data; - /** per-method service config data */ - grpc_core::RefCountedPtr method_params_table; + + /* external_connectivity_watcher_list head is guarded by its own mutex, since + * counts need to be grabbed immediately without polling on a cq */ + gpr_mu external_connectivity_watcher_list_mu; + struct external_connectivity_watcher* external_connectivity_watcher_list_head; /* the following properties are guarded by a mutex since APIs require them to be instantaneously available */ gpr_mu info_mu; grpc_core::UniquePtr info_lb_policy_name; + /** service config in JSON form */ grpc_core::UniquePtr info_service_config_json; - - grpc_connectivity_state_tracker state_tracker; - grpc_error* disconnect_error; - - /* external_connectivity_watcher_list head is guarded by its own mutex, since - * counts need to be grabbed immediately without polling on a cq */ - gpr_mu external_connectivity_watcher_list_mu; - struct external_connectivity_watcher* external_connectivity_watcher_list_head; } channel_data; -// Forward declarations. -static void start_pick_locked(void* arg, grpc_error* ignored); -static void maybe_apply_service_config_to_call_locked(grpc_call_element* elem); - -static const char* get_channel_connectivity_state_change_string( - grpc_connectivity_state state) { - switch (state) { - case GRPC_CHANNEL_IDLE: - return "Channel state change to IDLE"; - case GRPC_CHANNEL_CONNECTING: - return "Channel state change to CONNECTING"; - case GRPC_CHANNEL_READY: - return "Channel state change to READY"; - case GRPC_CHANNEL_TRANSIENT_FAILURE: - return "Channel state change to TRANSIENT_FAILURE"; - case GRPC_CHANNEL_SHUTDOWN: - return "Channel state change to SHUTDOWN"; - } - GPR_UNREACHABLE_CODE(return "UNKNOWN"); -} - -static void set_connectivity_state_and_picker_locked( - channel_data* chand, grpc_connectivity_state state, grpc_error* state_error, - const char* reason, - grpc_core::UniquePtr picker) { - // Update connectivity state. - grpc_connectivity_state_set(&chand->state_tracker, state, state_error, - reason); - if (chand->channelz_node != nullptr) { - chand->channelz_node->AddTraceEvent( - grpc_core::channelz::ChannelTrace::Severity::Info, - grpc_slice_from_static_string( - get_channel_connectivity_state_change_string(state))); - } - // Update picker. - chand->picker = std::move(picker); - // Re-process queued picks. - for (QueuedPick* pick = chand->queued_picks; pick != nullptr; - pick = pick->next) { - start_pick_locked(pick->elem, GRPC_ERROR_NONE); - } -} - -namespace grpc_core { -namespace { - -class ClientChannelControlHelper - : public LoadBalancingPolicy::ChannelControlHelper { - public: - explicit ClientChannelControlHelper(channel_data* chand) : chand_(chand) { - GRPC_CHANNEL_STACK_REF(chand_->owning_stack, "ClientChannelControlHelper"); - } - - ~ClientChannelControlHelper() override { - GRPC_CHANNEL_STACK_UNREF(chand_->owning_stack, - "ClientChannelControlHelper"); - } - - Subchannel* CreateSubchannel(const grpc_channel_args& args) override { - grpc_arg arg = SubchannelPoolInterface::CreateChannelArg( - chand_->subchannel_pool.get()); - grpc_channel_args* new_args = - grpc_channel_args_copy_and_add(&args, &arg, 1); - Subchannel* subchannel = grpc_client_channel_factory_create_subchannel( - chand_->client_channel_factory, new_args); - grpc_channel_args_destroy(new_args); - return subchannel; - } - - grpc_channel* CreateChannel(const char* target, grpc_client_channel_type type, - const grpc_channel_args& args) override { - return grpc_client_channel_factory_create_channel( - chand_->client_channel_factory, target, type, &args); - } - - void UpdateState( - grpc_connectivity_state state, grpc_error* state_error, - UniquePtr picker) override { - if (grpc_client_channel_trace.enabled()) { - const char* extra = chand_->disconnect_error == GRPC_ERROR_NONE - ? "" - : " (ignoring -- channel shutting down)"; - gpr_log(GPR_INFO, "chand=%p: update: state=%s error=%s picker=%p%s", - chand_, grpc_connectivity_state_name(state), - grpc_error_string(state_error), picker.get(), extra); - } - // Do update only if not shutting down. - if (chand_->disconnect_error == GRPC_ERROR_NONE) { - set_connectivity_state_and_picker_locked(chand_, state, state_error, - "helper", std::move(picker)); - } else { - GRPC_ERROR_UNREF(state_error); - } - } - - // No-op -- we should never get this from ResolvingLoadBalancingPolicy. - void RequestReresolution() override {} - - private: - channel_data* chand_; -}; - -} // namespace -} // namespace grpc_core - -// Synchronous callback from chand->resolving_lb_policy to process a resolver +// Synchronous callback from chand->request_router to process a resolver // result update. static bool process_resolver_result_locked(void* arg, const grpc_channel_args& args, const char** lb_policy_name, grpc_json** lb_policy_config) { channel_data* chand = static_cast(arg); - chand->have_service_config = true; ProcessedResolverResult resolver_result(args, chand->enable_retries); grpc_core::UniquePtr service_config_json = resolver_result.service_config_json(); @@ -279,38 +148,9 @@ static bool process_resolver_result_locked(void* arg, // Return results. *lb_policy_name = chand->info_lb_policy_name.get(); *lb_policy_config = resolver_result.lb_policy_config(); - // Apply service config to queued picks. - for (QueuedPick* pick = chand->queued_picks; pick != nullptr; - pick = pick->next) { - maybe_apply_service_config_to_call_locked(pick->elem); - } return service_config_changed; } -static grpc_error* do_ping_locked(channel_data* chand, grpc_transport_op* op) { - grpc_error* error = GRPC_ERROR_NONE; - grpc_connectivity_state state = - grpc_connectivity_state_get(&chand->state_tracker, &error); - if (state != GRPC_CHANNEL_READY) { - grpc_error* new_error = GRPC_ERROR_CREATE_REFERENCING_FROM_STATIC_STRING( - "channel not connected", &error, 1); - GRPC_ERROR_UNREF(error); - return new_error; - } - LoadBalancingPolicy::PickState pick; - chand->picker->Pick(&pick, &error); - if (pick.connected_subchannel != nullptr) { - pick.connected_subchannel->Ping(op->send_ping.on_initiate, - op->send_ping.on_ack); - } else { - if (error == GRPC_ERROR_NONE) { - error = GRPC_ERROR_CREATE_FROM_STATIC_STRING( - "LB policy dropped call on ping"); - } - } - return error; -} - static void start_transport_op_locked(void* arg, grpc_error* error_ignored) { grpc_transport_op* op = static_cast(arg); grpc_channel_element* elem = @@ -318,40 +158,47 @@ static void start_transport_op_locked(void* arg, grpc_error* error_ignored) { channel_data* chand = static_cast(elem->channel_data); if (op->on_connectivity_state_change != nullptr) { - grpc_connectivity_state_notify_on_state_change( - &chand->state_tracker, op->connectivity_state, - op->on_connectivity_state_change); + chand->request_router->NotifyOnConnectivityStateChange( + op->connectivity_state, op->on_connectivity_state_change); op->on_connectivity_state_change = nullptr; op->connectivity_state = nullptr; } if (op->send_ping.on_initiate != nullptr || op->send_ping.on_ack != nullptr) { - grpc_error* error = do_ping_locked(chand, op); - if (error != GRPC_ERROR_NONE) { + if (chand->request_router->lb_policy() == nullptr) { + grpc_error* error = + GRPC_ERROR_CREATE_FROM_STATIC_STRING("Ping with no load balancing"); GRPC_CLOSURE_SCHED(op->send_ping.on_initiate, GRPC_ERROR_REF(error)); GRPC_CLOSURE_SCHED(op->send_ping.on_ack, error); + } else { + grpc_error* error = GRPC_ERROR_NONE; + grpc_core::LoadBalancingPolicy::PickState pick_state; + // Pick must return synchronously, because pick_state.on_complete is null. + GPR_ASSERT( + chand->request_router->lb_policy()->PickLocked(&pick_state, &error)); + if (pick_state.connected_subchannel != nullptr) { + pick_state.connected_subchannel->Ping(op->send_ping.on_initiate, + op->send_ping.on_ack); + } else { + if (error == GRPC_ERROR_NONE) { + error = GRPC_ERROR_CREATE_FROM_STATIC_STRING( + "LB policy dropped call on ping"); + } + GRPC_CLOSURE_SCHED(op->send_ping.on_initiate, GRPC_ERROR_REF(error)); + GRPC_CLOSURE_SCHED(op->send_ping.on_ack, error); + } + op->bind_pollset = nullptr; } - op->bind_pollset = nullptr; op->send_ping.on_initiate = nullptr; op->send_ping.on_ack = nullptr; } - if (op->reset_connect_backoff) { - chand->resolving_lb_policy->ResetBackoffLocked(); + if (op->disconnect_with_error != GRPC_ERROR_NONE) { + chand->request_router->ShutdownLocked(op->disconnect_with_error); } - if (op->disconnect_with_error != GRPC_ERROR_NONE) { - chand->disconnect_error = op->disconnect_with_error; - grpc_pollset_set_del_pollset_set( - chand->resolving_lb_policy->interested_parties(), - chand->interested_parties); - chand->resolving_lb_policy.reset(); - set_connectivity_state_and_picker_locked( - chand, GRPC_CHANNEL_SHUTDOWN, GRPC_ERROR_REF(op->disconnect_with_error), - "shutdown from API", - grpc_core::UniquePtr( - grpc_core::New( - GRPC_ERROR_REF(op->disconnect_with_error)))); + if (op->reset_connect_backoff) { + chand->request_router->ResetConnectionBackoffLocked(); } GRPC_CHANNEL_STACK_UNREF(chand->owning_stack, "start_transport_op"); @@ -397,9 +244,6 @@ static grpc_error* cc_init_channel_elem(grpc_channel_element* elem, GPR_ASSERT(elem->filter == &grpc_client_channel_filter); // Initialize data members. chand->combiner = grpc_combiner_create(); - grpc_connectivity_state_init(&chand->state_tracker, GRPC_CHANNEL_IDLE, - "client_channel"); - chand->disconnect_error = GRPC_ERROR_NONE; gpr_mu_init(&chand->info_mu); gpr_mu_init(&chand->external_connectivity_watcher_list_mu); @@ -431,9 +275,8 @@ static grpc_error* cc_init_channel_elem(grpc_channel_element* elem, return GRPC_ERROR_CREATE_FROM_STATIC_STRING( "client channel factory arg must be a pointer"); } - chand->client_channel_factory = + grpc_client_channel_factory* client_channel_factory = static_cast(arg->value.pointer.p); - grpc_client_channel_factory_ref(chand->client_channel_factory); // Get server name to resolve, using proxy mapper if needed. arg = grpc_channel_args_find(args->channel_args, GRPC_ARG_SERVER_URI); if (arg == nullptr) { @@ -448,71 +291,26 @@ static grpc_error* cc_init_channel_elem(grpc_channel_element* elem, grpc_channel_args* new_args = nullptr; grpc_proxy_mappers_map_name(arg->value.string, args->channel_args, &proxy_name, &new_args); - grpc_core::UniquePtr target_uri( - proxy_name != nullptr ? proxy_name : gpr_strdup(arg->value.string)); - // Instantiate subchannel pool. - arg = grpc_channel_args_find(args->channel_args, - GRPC_ARG_USE_LOCAL_SUBCHANNEL_POOL); - if (grpc_channel_arg_get_bool(arg, false)) { - chand->subchannel_pool = - grpc_core::MakeRefCounted(); - } else { - chand->subchannel_pool = grpc_core::GlobalSubchannelPool::instance(); - } - // Instantiate resolving LB policy. - LoadBalancingPolicy::Args lb_args; - lb_args.combiner = chand->combiner; - lb_args.channel_control_helper = - grpc_core::UniquePtr( - grpc_core::New(chand)); - lb_args.args = new_args != nullptr ? new_args : args->channel_args; + // Instantiate request router. + grpc_client_channel_factory_ref(client_channel_factory); grpc_error* error = GRPC_ERROR_NONE; - chand->resolving_lb_policy.reset( - grpc_core::New( - std::move(lb_args), &grpc_client_channel_trace, std::move(target_uri), - process_resolver_result_locked, chand, &error)); + chand->request_router.Init( + chand->owning_stack, chand->combiner, client_channel_factory, + chand->interested_parties, &grpc_client_channel_trace, + process_resolver_result_locked, chand, + proxy_name != nullptr ? proxy_name : arg->value.string /* target_uri */, + new_args != nullptr ? new_args : args->channel_args, &error); + gpr_free(proxy_name); grpc_channel_args_destroy(new_args); - if (error != GRPC_ERROR_NONE) { - // Orphan the resolving LB policy and flush the exec_ctx to ensure - // that it finishes shutting down. This ensures that if we are - // failing, we destroy the ClientChannelControlHelper (and thus - // unref the channel stack) before we return. - // TODO(roth): This is not a complete solution, because it only - // catches the case where channel stack initialization fails in this - // particular filter. If there is a failure in a different filter, we - // will leave a dangling ref here, which can cause a crash. Fortunately, - // in practice, there are no other filters that can cause failures in - // channel stack initialization, so this works for now. - chand->resolving_lb_policy.reset(); - grpc_core::ExecCtx::Get()->Flush(); - } else { - grpc_pollset_set_add_pollset_set( - chand->resolving_lb_policy->interested_parties(), - chand->interested_parties); - if (grpc_client_channel_trace.enabled()) { - gpr_log(GPR_INFO, "chand=%p: created resolving_lb_policy=%p", chand, - chand->resolving_lb_policy.get()); - } - } return error; } /* Destructor for channel_data */ static void cc_destroy_channel_elem(grpc_channel_element* elem) { channel_data* chand = static_cast(elem->channel_data); - if (chand->resolving_lb_policy != nullptr) { - grpc_pollset_set_del_pollset_set( - chand->resolving_lb_policy->interested_parties(), - chand->interested_parties); - chand->resolving_lb_policy.reset(); - } + chand->request_router.Destroy(); // TODO(roth): Once we convert the filter API to C++, there will no // longer be any need to explicitly reset these smart pointer data members. - chand->picker.reset(); - chand->subchannel_pool.reset(); - if (chand->client_channel_factory != nullptr) { - grpc_client_channel_factory_unref(chand->client_channel_factory); - } chand->info_lb_policy_name.reset(); chand->info_service_config_json.reset(); chand->retry_throttle_data.reset(); @@ -520,8 +318,6 @@ static void cc_destroy_channel_elem(grpc_channel_element* elem) { grpc_client_channel_stop_backup_polling(chand->interested_parties); grpc_pollset_set_destroy(chand->interested_parties); GRPC_COMBINER_UNREF(chand->combiner, "client_channel"); - GRPC_ERROR_UNREF(chand->disconnect_error); - grpc_connectivity_state_destroy(&chand->state_tracker); gpr_mu_destroy(&chand->info_mu); gpr_mu_destroy(&chand->external_connectivity_watcher_list_mu); } @@ -575,12 +371,6 @@ static void cc_destroy_channel_elem(grpc_channel_element* elem) { // (census filter is on top of this one) // - add census stats for retries -namespace grpc_core { -namespace { -class QueuedPickCanceller; -} // namespace -} // namespace grpc_core - namespace { struct call_data; @@ -719,11 +509,8 @@ struct call_data { for (size_t i = 0; i < GPR_ARRAY_SIZE(pending_batches); ++i) { GPR_ASSERT(pending_batches[i].batch == nullptr); } - for (size_t i = 0; i < GRPC_CONTEXT_COUNT; ++i) { - if (pick.pick.subchannel_call_context[i].destroy != nullptr) { - pick.pick.subchannel_call_context[i].destroy( - pick.pick.subchannel_call_context[i].value); - } + if (have_request) { + request.Destroy(); } } @@ -750,10 +537,8 @@ struct call_data { // Set when we get a cancel_stream op. grpc_error* cancel_error = GRPC_ERROR_NONE; - QueuedPick pick; - bool pick_queued = false; - bool service_config_applied = false; - grpc_core::QueuedPickCanceller* pick_canceller = nullptr; + grpc_core::ManualConstructor request; + bool have_request = false; grpc_closure pick_closure; grpc_polling_entity* pollent = nullptr; @@ -815,7 +600,7 @@ static void retry_commit(grpc_call_element* elem, static void start_internal_recv_trailing_metadata(grpc_call_element* elem); static void on_complete(void* arg, grpc_error* error); static void start_retriable_subchannel_batches(void* arg, grpc_error* ignored); -static void remove_call_from_queued_picks_locked(grpc_call_element* elem); +static void start_pick_locked(void* arg, grpc_error* ignored); // // send op data caching @@ -943,7 +728,7 @@ static void free_cached_send_op_data_for_completed_batch( // void maybe_inject_recv_trailing_metadata_ready_for_lb( - const LoadBalancingPolicy::PickState& pick, + const grpc_core::LoadBalancingPolicy::PickState& pick, grpc_transport_stream_op_batch* batch) { if (pick.recv_trailing_metadata_ready != nullptr) { *pick.original_recv_trailing_metadata_ready = @@ -1061,25 +846,10 @@ static void fail_pending_batch_in_call_combiner(void* arg, grpc_error* error) { } // This is called via the call combiner, so access to calld is synchronized. -// If yield_call_combiner_predicate returns true, assumes responsibility for -// yielding the call combiner. -typedef bool (*YieldCallCombinerPredicate)( - const grpc_core::CallCombinerClosureList& closures); -static bool yield_call_combiner( - const grpc_core::CallCombinerClosureList& closures) { - return true; -} -static bool no_yield_call_combiner( - const grpc_core::CallCombinerClosureList& closures) { - return false; -} -static bool yield_call_combiner_if_pending_batches_found( - const grpc_core::CallCombinerClosureList& closures) { - return closures.size() > 0; -} -static void pending_batches_fail( - grpc_call_element* elem, grpc_error* error, - YieldCallCombinerPredicate yield_call_combiner_predicate) { +// If yield_call_combiner is true, assumes responsibility for yielding +// the call combiner. +static void pending_batches_fail(grpc_call_element* elem, grpc_error* error, + bool yield_call_combiner) { GPR_ASSERT(error != GRPC_ERROR_NONE); call_data* calld = static_cast(elem->call_data); if (grpc_client_channel_trace.enabled()) { @@ -1096,9 +866,9 @@ static void pending_batches_fail( pending_batch* pending = &calld->pending_batches[i]; grpc_transport_stream_op_batch* batch = pending->batch; if (batch != nullptr) { - if (batch->recv_trailing_metadata) { - maybe_inject_recv_trailing_metadata_ready_for_lb(calld->pick.pick, - batch); + if (batch->recv_trailing_metadata && calld->have_request) { + maybe_inject_recv_trailing_metadata_ready_for_lb( + *calld->request->pick(), batch); } batch->handler_private.extra_arg = calld; GRPC_CLOSURE_INIT(&batch->handler_private.closure, @@ -1109,7 +879,7 @@ static void pending_batches_fail( pending_batch_clear(calld, pending); } } - if (yield_call_combiner_predicate(closures)) { + if (yield_call_combiner) { closures.RunClosures(calld->call_combiner); } else { closures.RunClosuresWithoutYielding(calld->call_combiner); @@ -1153,8 +923,8 @@ static void pending_batches_resume(grpc_call_element* elem) { grpc_transport_stream_op_batch* batch = pending->batch; if (batch != nullptr) { if (batch->recv_trailing_metadata) { - maybe_inject_recv_trailing_metadata_ready_for_lb(calld->pick.pick, - batch); + maybe_inject_recv_trailing_metadata_ready_for_lb( + *calld->request->pick(), batch); } batch->handler_private.extra_arg = calld->subchannel_call.get(); GRPC_CLOSURE_INIT(&batch->handler_private.closure, @@ -1245,9 +1015,11 @@ static void do_retry(grpc_call_element* elem, const ClientChannelMethodParams::RetryPolicy* retry_policy = calld->method_params->retry_policy(); GPR_ASSERT(retry_policy != nullptr); - // Reset subchannel call and connected subchannel. calld->subchannel_call.reset(); - calld->pick.pick.connected_subchannel.reset(); + if (calld->have_request) { + calld->have_request = false; + calld->request.Destroy(); + } // Compute backoff delay. grpc_millis next_attempt_time; if (server_pushback_ms >= 0) { @@ -2166,7 +1938,7 @@ static void add_retriable_recv_trailing_metadata_op( batch_data->batch.payload->recv_trailing_metadata .recv_trailing_metadata_ready = &retry_state->recv_trailing_metadata_ready; - maybe_inject_recv_trailing_metadata_ready_for_lb(calld->pick.pick, + maybe_inject_recv_trailing_metadata_ready_for_lb(*calld->request->pick(), &batch_data->batch); } @@ -2435,38 +2207,41 @@ static void start_retriable_subchannel_batches(void* arg, grpc_error* ignored) { // LB pick // -static void create_subchannel_call(grpc_call_element* elem) { +static void create_subchannel_call(grpc_call_element* elem, grpc_error* error) { channel_data* chand = static_cast(elem->channel_data); call_data* calld = static_cast(elem->call_data); const size_t parent_data_size = calld->enable_retries ? sizeof(subchannel_call_retry_state) : 0; const grpc_core::ConnectedSubchannel::CallArgs call_args = { - calld->pollent, // pollent - calld->path, // path - calld->call_start_time, // start_time - calld->deadline, // deadline - calld->arena, // arena - calld->pick.pick.subchannel_call_context, // context - calld->call_combiner, // call_combiner - parent_data_size // parent_data_size + calld->pollent, // pollent + calld->path, // path + calld->call_start_time, // start_time + calld->deadline, // deadline + calld->arena, // arena + calld->request->pick()->subchannel_call_context, // context + calld->call_combiner, // call_combiner + parent_data_size // parent_data_size }; - grpc_error* error = GRPC_ERROR_NONE; + grpc_error* new_error = GRPC_ERROR_NONE; calld->subchannel_call = - calld->pick.pick.connected_subchannel->CreateCall(call_args, &error); + calld->request->pick()->connected_subchannel->CreateCall(call_args, + &new_error); if (grpc_client_channel_trace.enabled()) { gpr_log(GPR_INFO, "chand=%p calld=%p: create subchannel_call=%p: error=%s", chand, calld, calld->subchannel_call.get(), - grpc_error_string(error)); + grpc_error_string(new_error)); } - if (GPR_UNLIKELY(error != GRPC_ERROR_NONE)) { - pending_batches_fail(elem, error, yield_call_combiner); + if (GPR_UNLIKELY(new_error != GRPC_ERROR_NONE)) { + new_error = grpc_error_add_child(new_error, error); + pending_batches_fail(elem, new_error, true /* yield_call_combiner */); } else { if (parent_data_size > 0) { - new (calld->subchannel_call->GetParentData()) - subchannel_call_retry_state(calld->pick.pick.subchannel_call_context); + new (calld->subchannel_call->GetParentData()) subchannel_call_retry_state( + calld->request->pick()->subchannel_call_context); } pending_batches_resume(elem); } + GRPC_ERROR_UNREF(error); } // Invoked when a pick is completed, on both success or failure. @@ -2474,106 +2249,54 @@ static void pick_done(void* arg, grpc_error* error) { grpc_call_element* elem = static_cast(arg); channel_data* chand = static_cast(elem->channel_data); call_data* calld = static_cast(elem->call_data); - if (error != GRPC_ERROR_NONE) { - if (grpc_client_channel_trace.enabled()) { - gpr_log(GPR_INFO, - "chand=%p calld=%p: failed to pick subchannel: error=%s", chand, - calld, grpc_error_string(error)); - } - pending_batches_fail(elem, GRPC_ERROR_REF(error), yield_call_combiner); - return; - } - create_subchannel_call(elem); -} - -namespace grpc_core { -namespace { - -// A class to handle the call combiner cancellation callback for a -// queued pick. -class QueuedPickCanceller { - public: - explicit QueuedPickCanceller(grpc_call_element* elem) : elem_(elem) { - auto* calld = static_cast(elem->call_data); - auto* chand = static_cast(elem->channel_data); - GRPC_CALL_STACK_REF(calld->owning_call, "QueuedPickCanceller"); - GRPC_CLOSURE_INIT(&closure_, &CancelLocked, this, - grpc_combiner_scheduler(chand->combiner)); - grpc_call_combiner_set_notify_on_cancel(calld->call_combiner, &closure_); - } - - private: - static void CancelLocked(void* arg, grpc_error* error) { - auto* self = static_cast(arg); - auto* chand = static_cast(self->elem_->channel_data); - auto* calld = static_cast(self->elem_->call_data); - if (grpc_client_channel_trace.enabled()) { - gpr_log(GPR_INFO, - "chand=%p calld=%p: cancelling queued pick: " - "error=%s self=%p calld->pick_canceller=%p", - chand, calld, grpc_error_string(error), self, - calld->pick_canceller); - } - if (calld->pick_canceller == self && error != GRPC_ERROR_NONE) { - // Remove pick from list of queued picks. - remove_call_from_queued_picks_locked(self->elem_); - // Fail pending batches on the call. - pending_batches_fail(self->elem_, GRPC_ERROR_REF(error), - yield_call_combiner_if_pending_batches_found); - } - GRPC_CALL_STACK_UNREF(calld->owning_call, "QueuedPickCanceller"); - Delete(self); - } - - grpc_call_element* elem_; - grpc_closure closure_; -}; - -} // namespace -} // namespace grpc_core - -// Removes the call from the channel's list of queued picks. -static void remove_call_from_queued_picks_locked(grpc_call_element* elem) { - auto* chand = static_cast(elem->channel_data); - auto* calld = static_cast(elem->call_data); - for (QueuedPick** pick = &chand->queued_picks; *pick != nullptr; - pick = &(*pick)->next) { - if (*pick == &calld->pick) { + if (GPR_UNLIKELY(calld->request->pick()->connected_subchannel == nullptr)) { + // Failed to create subchannel. + // If there was no error, this is an LB policy drop, in which case + // we return an error; otherwise, we may retry. + grpc_status_code status = GRPC_STATUS_OK; + grpc_error_get_status(error, calld->deadline, &status, nullptr, nullptr, + nullptr); + if (error == GRPC_ERROR_NONE || !calld->enable_retries || + !maybe_retry(elem, nullptr /* batch_data */, status, + nullptr /* server_pushback_md */)) { + grpc_error* new_error = + error == GRPC_ERROR_NONE + ? GRPC_ERROR_CREATE_FROM_STATIC_STRING( + "Call dropped by load balancing policy") + : GRPC_ERROR_CREATE_REFERENCING_FROM_STATIC_STRING( + "Failed to create subchannel", &error, 1); if (grpc_client_channel_trace.enabled()) { - gpr_log(GPR_INFO, "chand=%p calld=%p: removing from queued picks list", - chand, calld); + gpr_log(GPR_INFO, + "chand=%p calld=%p: failed to create subchannel: error=%s", + chand, calld, grpc_error_string(new_error)); } - calld->pick_queued = false; - *pick = calld->pick.next; - // Remove call's pollent from channel's interested_parties. - grpc_polling_entity_del_from_pollset_set(calld->pollent, - chand->interested_parties); - // Lame the call combiner canceller. - calld->pick_canceller = nullptr; - break; + pending_batches_fail(elem, new_error, true /* yield_call_combiner */); } + } else { + /* Create call on subchannel. */ + create_subchannel_call(elem, GRPC_ERROR_REF(error)); } } -// Adds the call to the channel's list of queued picks. -static void add_call_to_queued_picks_locked(grpc_call_element* elem) { - auto* chand = static_cast(elem->channel_data); - auto* calld = static_cast(elem->call_data); - if (grpc_client_channel_trace.enabled()) { - gpr_log(GPR_INFO, "chand=%p calld=%p: adding to queued picks list", chand, - calld); +// If the channel is in TRANSIENT_FAILURE and the call is not +// wait_for_ready=true, fails the call and returns true. +static bool fail_call_if_in_transient_failure(grpc_call_element* elem) { + channel_data* chand = static_cast(elem->channel_data); + call_data* calld = static_cast(elem->call_data); + grpc_transport_stream_op_batch* batch = calld->pending_batches[0].batch; + if (chand->request_router->GetConnectivityState() == + GRPC_CHANNEL_TRANSIENT_FAILURE && + (batch->payload->send_initial_metadata.send_initial_metadata_flags & + GRPC_INITIAL_METADATA_WAIT_FOR_READY) == 0) { + pending_batches_fail( + elem, + grpc_error_set_int(GRPC_ERROR_CREATE_FROM_STATIC_STRING( + "channel is in state TRANSIENT_FAILURE"), + GRPC_ERROR_INT_GRPC_STATUS, GRPC_STATUS_UNAVAILABLE), + true /* yield_call_combiner */); + return true; } - calld->pick_queued = true; - // Add call to queued picks list. - calld->pick.elem = elem; - calld->pick.next = chand->queued_picks; - chand->queued_picks = &calld->pick; - // Add call's pollent to channel's interested_parties, so that I/O - // can be done under the call's CQ. - grpc_polling_entity_add_to_pollset_set(calld->pollent, - chand->interested_parties); - // Register call combiner cancellation callback. - calld->pick_canceller = grpc_core::New(elem); + return false; } // Applies service config to the call. Must be invoked once we know @@ -2633,37 +2356,36 @@ static void apply_service_config_to_call_locked(grpc_call_element* elem) { } // Invoked once resolver results are available. -static void maybe_apply_service_config_to_call_locked(grpc_call_element* elem) { - channel_data* chand = static_cast(elem->channel_data); +static bool maybe_apply_service_config_to_call_locked(void* arg) { + grpc_call_element* elem = static_cast(arg); call_data* calld = static_cast(elem->call_data); - // Apply service config data to the call only once, and only if the - // channel has the data available. - if (GPR_LIKELY(chand->have_service_config && - !calld->service_config_applied)) { - calld->service_config_applied = true; + // Only get service config data on the first attempt. + if (GPR_LIKELY(calld->num_attempts_completed == 0)) { apply_service_config_to_call_locked(elem); + // Check this after applying service config, since it may have + // affected the call's wait_for_ready value. + if (fail_call_if_in_transient_failure(elem)) return false; } + return true; } -static const char* pick_result_name( - LoadBalancingPolicy::SubchannelPicker::PickResult result) { - switch (result) { - case LoadBalancingPolicy::SubchannelPicker::PICK_COMPLETE: - return "COMPLETE"; - case LoadBalancingPolicy::SubchannelPicker::PICK_QUEUE: - return "QUEUE"; - case LoadBalancingPolicy::SubchannelPicker::PICK_TRANSIENT_FAILURE: - return "TRANSIENT_FAILURE"; - } - GPR_UNREACHABLE_CODE(return "UNKNOWN"); -} - -static void start_pick_locked(void* arg, grpc_error* error) { +static void start_pick_locked(void* arg, grpc_error* ignored) { grpc_call_element* elem = static_cast(arg); call_data* calld = static_cast(elem->call_data); channel_data* chand = static_cast(elem->channel_data); - GPR_ASSERT(calld->pick.pick.connected_subchannel == nullptr); + GPR_ASSERT(!calld->have_request); GPR_ASSERT(calld->subchannel_call == nullptr); + // Normally, we want to do this check until after we've processed the + // service config, so that we can honor the wait_for_ready setting in + // the service config. However, if the channel is in TRANSIENT_FAILURE + // and we don't have an LB policy at this point, that means that the + // resolver has returned a failure, so we're not going to get a service + // config right away. In that case, we fail the call now based on the + // wait_for_ready value passed in from the application. + if (chand->request_router->lb_policy() == nullptr && + fail_call_if_in_transient_failure(elem)) { + return; + } // If this is a retry, use the send_initial_metadata payload that // we've cached; otherwise, use the pending batch. The // send_initial_metadata batch will be the first pending batch in the @@ -2674,78 +2396,25 @@ static void start_pick_locked(void* arg, grpc_error* error) { // allocate the subchannel batch earlier so that we can give the // subchannel's copy of the metadata batch (which is copied for each // attempt) to the LB policy instead the one from the parent channel. - calld->pick.pick.initial_metadata = + grpc_metadata_batch* initial_metadata = calld->seen_send_initial_metadata ? &calld->send_initial_metadata : calld->pending_batches[0] .batch->payload->send_initial_metadata.send_initial_metadata; - uint32_t* send_initial_metadata_flags = + uint32_t* initial_metadata_flags = calld->seen_send_initial_metadata ? &calld->send_initial_metadata_flags : &calld->pending_batches[0] .batch->payload->send_initial_metadata .send_initial_metadata_flags; - // Apply service config to call if needed. - maybe_apply_service_config_to_call_locked(elem); - // When done, we schedule this closure to leave the channel combiner. GRPC_CLOSURE_INIT(&calld->pick_closure, pick_done, elem, grpc_schedule_on_exec_ctx); - // Attempt pick. - error = GRPC_ERROR_NONE; - auto pick_result = chand->picker->Pick(&calld->pick.pick, &error); - if (grpc_client_channel_trace.enabled()) { - gpr_log(GPR_INFO, - "chand=%p calld=%p: LB pick returned %s (connected_subchannel=%p, " - "error=%s)", - chand, calld, pick_result_name(pick_result), - calld->pick.pick.connected_subchannel.get(), - grpc_error_string(error)); - } - switch (pick_result) { - case LoadBalancingPolicy::SubchannelPicker::PICK_TRANSIENT_FAILURE: - // If we're shutting down, fail all RPCs. - if (chand->disconnect_error != GRPC_ERROR_NONE) { - GRPC_ERROR_UNREF(error); - GRPC_CLOSURE_SCHED(&calld->pick_closure, - GRPC_ERROR_REF(chand->disconnect_error)); - break; - } - // If wait_for_ready is false, then the error indicates the RPC - // attempt's final status. - if ((*send_initial_metadata_flags & - GRPC_INITIAL_METADATA_WAIT_FOR_READY) == 0) { - // Retry if appropriate; otherwise, fail. - grpc_status_code status = GRPC_STATUS_OK; - grpc_error_get_status(error, calld->deadline, &status, nullptr, nullptr, - nullptr); - if (!calld->enable_retries || - !maybe_retry(elem, nullptr /* batch_data */, status, - nullptr /* server_pushback_md */)) { - grpc_error* new_error = - GRPC_ERROR_CREATE_REFERENCING_FROM_STATIC_STRING( - "Failed to create subchannel", &error, 1); - GRPC_ERROR_UNREF(error); - GRPC_CLOSURE_SCHED(&calld->pick_closure, new_error); - } - if (calld->pick_queued) remove_call_from_queued_picks_locked(elem); - break; - } - // If wait_for_ready is true, then queue to retry when we get a new - // picker. - GRPC_ERROR_UNREF(error); - // Fallthrough - case LoadBalancingPolicy::SubchannelPicker::PICK_QUEUE: - if (!calld->pick_queued) add_call_to_queued_picks_locked(elem); - break; - default: // PICK_COMPLETE - // Handle drops. - if (GPR_UNLIKELY(calld->pick.pick.connected_subchannel == nullptr)) { - error = GRPC_ERROR_CREATE_FROM_STATIC_STRING( - "Call dropped by load balancing policy"); - } - GRPC_CLOSURE_SCHED(&calld->pick_closure, error); - if (calld->pick_queued) remove_call_from_queued_picks_locked(elem); - } + calld->request.Init(calld->owning_call, calld->call_combiner, calld->pollent, + initial_metadata, initial_metadata_flags, + maybe_apply_service_config_to_call_locked, elem, + &calld->pick_closure); + calld->have_request = true; + chand->request_router->RouteCallLocked(calld->request.get()); } // @@ -2789,10 +2458,8 @@ static void cc_start_transport_stream_op_batch( // been started), fail all pending batches. Otherwise, send the // cancellation down to the subchannel call. if (calld->subchannel_call == nullptr) { - // TODO(roth): If there is a pending retry callback, do we need to - // cancel it here? pending_batches_fail(elem, GRPC_ERROR_REF(calld->cancel_error), - no_yield_call_combiner); + false /* yield_call_combiner */); // Note: This will release the call combiner. grpc_transport_stream_op_batch_finish_with_failure( batch, GRPC_ERROR_REF(calld->cancel_error), calld->call_combiner); @@ -2889,8 +2556,7 @@ const grpc_channel_filter grpc_client_channel_filter = { void grpc_client_channel_set_channelz_node( grpc_channel_element* elem, grpc_core::channelz::ClientChannelNode* node) { channel_data* chand = static_cast(elem->channel_data); - chand->channelz_node = node; - chand->resolving_lb_policy->set_channelz_node(node->Ref()); + chand->request_router->set_channelz_node(node); } void grpc_client_channel_populate_child_refs( @@ -2898,23 +2564,22 @@ void grpc_client_channel_populate_child_refs( grpc_core::channelz::ChildRefsList* child_subchannels, grpc_core::channelz::ChildRefsList* child_channels) { channel_data* chand = static_cast(elem->channel_data); - if (chand->resolving_lb_policy != nullptr) { - chand->resolving_lb_policy->FillChildRefsForChannelz(child_subchannels, - child_channels); + if (chand->request_router->lb_policy() != nullptr) { + chand->request_router->lb_policy()->FillChildRefsForChannelz( + child_subchannels, child_channels); } } static void try_to_connect_locked(void* arg, grpc_error* error_ignored) { channel_data* chand = static_cast(arg); - chand->resolving_lb_policy->ExitIdleLocked(); + chand->request_router->ExitIdleLocked(); GRPC_CHANNEL_STACK_UNREF(chand->owning_stack, "try_to_connect"); } grpc_connectivity_state grpc_client_channel_check_connectivity_state( grpc_channel_element* elem, int try_to_connect) { channel_data* chand = static_cast(elem->channel_data); - grpc_connectivity_state out = - grpc_connectivity_state_check(&chand->state_tracker); + grpc_connectivity_state out = chand->request_router->GetConnectivityState(); if (out == GRPC_CHANNEL_IDLE && try_to_connect) { GRPC_CHANNEL_STACK_REF(chand->owning_stack, "try_to_connect"); GRPC_CLOSURE_SCHED( @@ -3023,15 +2688,15 @@ static void watch_connectivity_state_locked(void* arg, GRPC_CLOSURE_RUN(w->watcher_timer_init, GRPC_ERROR_NONE); GRPC_CLOSURE_INIT(&w->my_closure, on_external_watch_complete_locked, w, grpc_combiner_scheduler(w->chand->combiner)); - grpc_connectivity_state_notify_on_state_change(&w->chand->state_tracker, - w->state, &w->my_closure); + w->chand->request_router->NotifyOnConnectivityStateChange(w->state, + &w->my_closure); } else { GPR_ASSERT(w->watcher_timer_init == nullptr); found = lookup_external_connectivity_watcher(w->chand, w->on_complete); if (found) { GPR_ASSERT(found->on_complete == w->on_complete); - grpc_connectivity_state_notify_on_state_change( - &found->chand->state_tracker, nullptr, &found->my_closure); + found->chand->request_router->NotifyOnConnectivityStateChange( + nullptr, &found->my_closure); } grpc_polling_entity_del_from_pollset_set(&w->pollent, w->chand->interested_parties); diff --git a/src/core/ext/filters/client_channel/lb_policy.cc b/src/core/ext/filters/client_channel/lb_policy.cc index 9e3477b9ed5..d9b3927d1ca 100644 --- a/src/core/ext/filters/client_channel/lb_policy.cc +++ b/src/core/ext/filters/client_channel/lb_policy.cc @@ -54,15 +54,35 @@ grpc_json* LoadBalancingPolicy::ParseLoadBalancingConfig( return nullptr; } -LoadBalancingPolicy::LoadBalancingPolicy(Args args, intptr_t initial_refcount) - : InternallyRefCounted(&grpc_trace_lb_policy_refcount, initial_refcount), +LoadBalancingPolicy::LoadBalancingPolicy(Args args) + : InternallyRefCounted(&grpc_trace_lb_policy_refcount), combiner_(GRPC_COMBINER_REF(args.combiner, "lb_policy")), + client_channel_factory_(args.client_channel_factory), + subchannel_pool_(std::move(args.subchannel_pool)), interested_parties_(grpc_pollset_set_create()), - channel_control_helper_(std::move(args.channel_control_helper)) {} + request_reresolution_(nullptr) {} LoadBalancingPolicy::~LoadBalancingPolicy() { grpc_pollset_set_destroy(interested_parties_); GRPC_COMBINER_UNREF(combiner_, "lb_policy"); } +void LoadBalancingPolicy::TryReresolutionLocked( + grpc_core::TraceFlag* grpc_lb_trace, grpc_error* error) { + if (request_reresolution_ != nullptr) { + GRPC_CLOSURE_SCHED(request_reresolution_, error); + request_reresolution_ = nullptr; + if (grpc_lb_trace->enabled()) { + gpr_log(GPR_INFO, + "%s %p: scheduling re-resolution closure with error=%s.", + grpc_lb_trace->name(), this, grpc_error_string(error)); + } + } else { + if (grpc_lb_trace->enabled()) { + gpr_log(GPR_INFO, "%s %p: no available re-resolution closure.", + grpc_lb_trace->name(), this); + } + } +} + } // namespace grpc_core diff --git a/src/core/ext/filters/client_channel/lb_policy.h b/src/core/ext/filters/client_channel/lb_policy.h index aeb8138a12e..56bf1951cfb 100644 --- a/src/core/ext/filters/client_channel/lb_policy.h +++ b/src/core/ext/filters/client_channel/lb_policy.h @@ -24,6 +24,7 @@ #include "src/core/ext/filters/client_channel/client_channel_channelz.h" #include "src/core/ext/filters/client_channel/client_channel_factory.h" #include "src/core/ext/filters/client_channel/subchannel.h" +#include "src/core/ext/filters/client_channel/subchannel_pool_interface.h" #include "src/core/lib/gprpp/abstract.h" #include "src/core/lib/gprpp/orphanable.h" #include "src/core/lib/gprpp/ref_counted_ptr.h" @@ -42,194 +43,61 @@ namespace grpc_core { /// /// Any I/O done by the LB policy should be done under the pollset_set /// returned by \a interested_parties(). -// TODO(roth): Once we move to EventManager-based polling, remove the -// interested_parties() hooks from the API. class LoadBalancingPolicy : public InternallyRefCounted { public: + struct Args { + /// The combiner under which all LB policy calls will be run. + /// Policy does NOT take ownership of the reference to the combiner. + // TODO(roth): Once we have a C++-like interface for combiners, this + // API should change to take a smart pointer that does pass ownership + // of a reference. + grpc_combiner* combiner = nullptr; + /// Used to create channels and subchannels. + grpc_client_channel_factory* client_channel_factory = nullptr; + /// Subchannel pool. + RefCountedPtr subchannel_pool; + /// Channel args from the resolver. + /// Note that the LB policy gets the set of addresses from the + /// GRPC_ARG_SERVER_ADDRESS_LIST channel arg. + grpc_channel_args* args = nullptr; + /// Load balancing config from the resolver. + grpc_json* lb_config = nullptr; + }; + /// State used for an LB pick. struct PickState { /// Initial metadata associated with the picking call. - /// This is both an input and output parameter; the LB policy may - /// use metadata here to influence its routing decision, and it may - /// add new metadata here to be sent with the call to the chosen backend. grpc_metadata_batch* initial_metadata = nullptr; + /// Pointer to bitmask used for selective cancelling. See + /// \a CancelMatchingPicksLocked() and \a GRPC_INITIAL_METADATA_* in + /// grpc_types.h. + uint32_t* initial_metadata_flags = nullptr; /// Storage for LB token in \a initial_metadata, or nullptr if not used. - // TODO(roth): Remove this from the API. Maybe have the LB policy - // allocate this on the arena instead? grpc_linked_mdelem lb_token_mdelem_storage; - /// Callback set by lb policy to be notified of trailing metadata. - /// The callback must be scheduled on grpc_schedule_on_exec_ctx. + /// Closure to run when pick is complete, if not completed synchronously. + /// If null, pick will fail if a result is not available synchronously. + grpc_closure* on_complete = nullptr; + // Callback set by lb policy to be notified of trailing metadata. + // The callback must be scheduled on grpc_schedule_on_exec_ctx. grpc_closure* recv_trailing_metadata_ready = nullptr; - /// The address that will be set to point to the original - /// recv_trailing_metadata_ready callback, to be invoked by the LB - /// policy's recv_trailing_metadata_ready callback when complete. - /// Must be non-null if recv_trailing_metadata_ready is non-null. + // The address that will be set to point to the original + // recv_trailing_metadata_ready callback, to be invoked by the LB + // policy's recv_trailing_metadata_ready callback when complete. + // Must be non-null if recv_trailing_metadata_ready is non-null. grpc_closure** original_recv_trailing_metadata_ready = nullptr; - /// If this is not nullptr, then the client channel will point it to the - /// call's trailing metadata before invoking recv_trailing_metadata_ready. - /// If this is nullptr, then the callback will still be called. - /// The lb does not have ownership of the metadata. + // If this is not nullptr, then the client channel will point it to the + // call's trailing metadata before invoking recv_trailing_metadata_ready. + // If this is nullptr, then the callback will still be called. + // The lb does not have ownership of the metadata. grpc_metadata_batch** recv_trailing_metadata = nullptr; /// Will be set to the selected subchannel, or nullptr on failure or when /// the LB policy decides to drop the call. RefCountedPtr connected_subchannel; /// Will be populated with context to pass to the subchannel call, if /// needed. - // TODO(roth): Remove this from the API, especially since it's not - // working properly anyway (see https://github.com/grpc/grpc/issues/15927). grpc_call_context_element subchannel_call_context[GRPC_CONTEXT_COUNT] = {}; - }; - - /// A picker is the object used to actual perform picks. - /// - /// Pickers are intended to encapsulate all of the state and logic - /// needed on the data plane (i.e., to actually process picks for - /// individual RPCs sent on the channel) while excluding all of the - /// state and logic needed on the control plane (i.e., resolver - /// updates, connectivity state notifications, etc); the latter should - /// live in the LB policy object itself. - /// - /// Currently, pickers are always accessed from within the - /// client_channel combiner, so they do not have to be thread-safe. - // TODO(roth): In a subsequent PR, split the data plane work (i.e., - // the interaction with the picker) and the control plane work (i.e., - // the interaction with the LB policy) into two different - // synchronization mechanisms, to avoid lock contention between the two. - class SubchannelPicker { - public: - enum PickResult { - // Pick complete. If connected_subchannel is non-null, client channel - // can immediately proceed with the call on connected_subchannel; - // otherwise, call should be dropped. - PICK_COMPLETE, - // Pick cannot be completed until something changes on the control - // plane. Client channel will queue the pick and try again the - // next time the picker is updated. - PICK_QUEUE, - // LB policy is in transient failure. If the pick is wait_for_ready, - // client channel will wait for the next picker and try again; - // otherwise, the call will be failed immediately (although it may - // be retried if the client channel is configured to do so). - // The Pick() method will set its error parameter if this value is - // returned. - PICK_TRANSIENT_FAILURE, - }; - - SubchannelPicker() = default; - virtual ~SubchannelPicker() = default; - - virtual PickResult Pick(PickState* pick, grpc_error** error) GRPC_ABSTRACT; - - GRPC_ABSTRACT_BASE_CLASS - }; - - // A picker that returns PICK_QUEUE for all picks. - // Also calls the parent LB policy's ExitIdleLocked() method when the - // first pick is seen. - class QueuePicker : public SubchannelPicker { - public: - explicit QueuePicker(RefCountedPtr parent) - : parent_(std::move(parent)) {} - - PickResult Pick(PickState* pick, grpc_error** error) override { - // We invoke the parent's ExitIdleLocked() via a closure instead - // of doing it directly here, for two reasons: - // 1. ExitIdleLocked() may cause the policy's state to change and - // a new picker to be delivered to the channel. If that new - // picker is delivered before ExitIdleLocked() returns, then by - // the time this function returns, the pick will already have - // been processed, and we'll be trying to re-process the same - // pick again, leading to a crash. - // 2. In a subsequent PR, we will split the data plane and control - // plane synchronization into separate combiners, at which - // point this will need to hop from the data plane combiner into - // the control plane combiner. - if (!exit_idle_called_) { - exit_idle_called_ = true; - parent_->Ref().release(); // ref held by closure. - GRPC_CLOSURE_SCHED( - GRPC_CLOSURE_CREATE(&CallExitIdle, parent_.get(), - grpc_combiner_scheduler(parent_->combiner())), - GRPC_ERROR_NONE); - } - return PICK_QUEUE; - } - - private: - static void CallExitIdle(void* arg, grpc_error* error) { - LoadBalancingPolicy* parent = static_cast(arg); - parent->ExitIdleLocked(); - parent->Unref(); - } - - RefCountedPtr parent_; - bool exit_idle_called_ = false; - }; - - // A picker that returns PICK_TRANSIENT_FAILURE for all picks. - class TransientFailurePicker : public SubchannelPicker { - public: - explicit TransientFailurePicker(grpc_error* error) : error_(error) {} - ~TransientFailurePicker() { GRPC_ERROR_UNREF(error_); } - - PickResult Pick(PickState* pick, grpc_error** error) override { - *error = GRPC_ERROR_REF(error_); - return PICK_TRANSIENT_FAILURE; - } - - private: - grpc_error* error_; - }; - - /// A proxy object used by the LB policy to communicate with the client - /// channel. - class ChannelControlHelper { - public: - ChannelControlHelper() = default; - virtual ~ChannelControlHelper() = default; - - /// Creates a new subchannel with the specified channel args. - virtual Subchannel* CreateSubchannel(const grpc_channel_args& args) - GRPC_ABSTRACT; - - /// Creates a channel with the specified target, type, and channel args. - virtual grpc_channel* CreateChannel( - const char* target, grpc_client_channel_type type, - const grpc_channel_args& args) GRPC_ABSTRACT; - - /// Sets the connectivity state and returns a new picker to be used - /// by the client channel. - virtual void UpdateState(grpc_connectivity_state state, - grpc_error* state_error, - UniquePtr picker) { - std::move(picker); // Suppress clang-tidy complaint. - // The rest of this is copied from the GRPC_ABSTRACT macro. - gpr_log(GPR_ERROR, "Function marked GRPC_ABSTRACT was not implemented"); - GPR_ASSERT(false); - } - - /// Requests that the resolver re-resolve. - virtual void RequestReresolution() GRPC_ABSTRACT; - - GRPC_ABSTRACT_BASE_CLASS - }; - - /// Args used to instantiate an LB policy. - struct Args { - /// The combiner under which all LB policy calls will be run. - /// Policy does NOT take ownership of the reference to the combiner. - // TODO(roth): Once we have a C++-like interface for combiners, this - // API should change to take a smart pointer that does pass ownership - // of a reference. - grpc_combiner* combiner = nullptr; - /// Channel control helper. - UniquePtr channel_control_helper; - /// Channel args from the resolver. - /// Note that the LB policy gets the set of addresses from the - /// GRPC_ARG_SERVER_ADDRESS_LIST channel arg. - const grpc_channel_args* args = nullptr; - /// Load balancing config from the resolver. - grpc_json* lb_config = nullptr; + /// Next pointer. For internal use by LB policy. + PickState* next = nullptr; }; // Not copyable nor movable. @@ -245,6 +113,48 @@ class LoadBalancingPolicy : public InternallyRefCounted { virtual void UpdateLocked(const grpc_channel_args& args, grpc_json* lb_config) GRPC_ABSTRACT; + /// Finds an appropriate subchannel for a call, based on data in \a pick. + /// \a pick must remain alive until the pick is complete. + /// + /// If a result is known immediately, returns true, setting \a *error + /// upon failure. Otherwise, \a pick->on_complete will be invoked once + /// the pick is complete with its error argument set to indicate success + /// or failure. + /// + /// If \a pick->on_complete is null and no result is known immediately, + /// a synchronous failure will be returned (i.e., \a *error will be + /// set and true will be returned). + virtual bool PickLocked(PickState* pick, grpc_error** error) GRPC_ABSTRACT; + + /// Cancels \a pick. + /// The \a on_complete callback of the pending pick will be invoked with + /// \a pick->connected_subchannel set to null. + virtual void CancelPickLocked(PickState* pick, + grpc_error* error) GRPC_ABSTRACT; + + /// Cancels all pending picks for which their \a initial_metadata_flags (as + /// given in the call to \a PickLocked()) matches + /// \a initial_metadata_flags_eq when ANDed with + /// \a initial_metadata_flags_mask. + virtual void CancelMatchingPicksLocked(uint32_t initial_metadata_flags_mask, + uint32_t initial_metadata_flags_eq, + grpc_error* error) GRPC_ABSTRACT; + + /// Requests a notification when the connectivity state of the policy + /// changes from \a *state. When that happens, sets \a *state to the + /// new state and schedules \a closure. + virtual void NotifyOnStateChangeLocked(grpc_connectivity_state* state, + grpc_closure* closure) GRPC_ABSTRACT; + + /// Returns the policy's current connectivity state. Sets \a error to + /// the associated error, if any. + virtual grpc_connectivity_state CheckConnectivityLocked( + grpc_error** connectivity_error) GRPC_ABSTRACT; + + /// Hands off pending picks to \a new_policy. + virtual void HandOffPendingPicksLocked(LoadBalancingPolicy* new_policy) + GRPC_ABSTRACT; + /// Tries to enter a READY connectivity state. /// TODO(roth): As part of restructuring how we handle IDLE state, /// consider whether this method is still needed. @@ -273,11 +183,18 @@ class LoadBalancingPolicy : public InternallyRefCounted { /// given the JSON node of a LoadBalancingConfig array. static grpc_json* ParseLoadBalancingConfig(const grpc_json* lb_config_array); + /// Sets the re-resolution closure to \a request_reresolution. + void SetReresolutionClosureLocked(grpc_closure* request_reresolution) { + GPR_ASSERT(request_reresolution_ == nullptr); + request_reresolution_ = request_reresolution; + } + grpc_pollset_set* interested_parties() const { return interested_parties_; } - void set_channelz_node( - RefCountedPtr channelz_node) { - channelz_node_ = std::move(channelz_node); + // Callers that need their own reference can call the returned + // object's Ref() method. + SubchannelPoolInterface* subchannel_pool() const { + return subchannel_pool_.get(); } GRPC_ABSTRACT_BASE_CLASS @@ -285,18 +202,12 @@ class LoadBalancingPolicy : public InternallyRefCounted { protected: GPRC_ALLOW_CLASS_TO_USE_NON_PUBLIC_DELETE - explicit LoadBalancingPolicy(Args args, intptr_t initial_refcount = 1); + explicit LoadBalancingPolicy(Args args); virtual ~LoadBalancingPolicy(); grpc_combiner* combiner() const { return combiner_; } - - // Note: This will return null after ShutdownLocked() has been called. - ChannelControlHelper* channel_control_helper() const { - return channel_control_helper_.get(); - } - - channelz::ClientChannelNode* channelz_node() const { - return channelz_node_.get(); + grpc_client_channel_factory* client_channel_factory() const { + return client_channel_factory_; } /// Shuts down the policy. Any pending picks that have not been @@ -304,22 +215,27 @@ class LoadBalancingPolicy : public InternallyRefCounted { /// failed. virtual void ShutdownLocked() GRPC_ABSTRACT; + /// Tries to request a re-resolution. + void TryReresolutionLocked(grpc_core::TraceFlag* grpc_lb_trace, + grpc_error* error); + private: static void ShutdownAndUnrefLocked(void* arg, grpc_error* ignored) { LoadBalancingPolicy* policy = static_cast(arg); policy->ShutdownLocked(); - policy->channel_control_helper_.reset(); policy->Unref(); } /// Combiner under which LB policy actions take place. grpc_combiner* combiner_; + /// Client channel factory, used to create channels and subchannels. + grpc_client_channel_factory* client_channel_factory_; + /// Subchannel pool. + RefCountedPtr subchannel_pool_; /// Owned pointer to interested parties in load balancing decisions. grpc_pollset_set* interested_parties_; - /// Channel control helper. - UniquePtr channel_control_helper_; - /// Channelz node. - RefCountedPtr channelz_node_; + /// Callback to force a re-resolution. + grpc_closure* request_reresolution_; }; } // namespace grpc_core diff --git a/src/core/ext/filters/client_channel/lb_policy/grpclb/grpclb.cc b/src/core/ext/filters/client_channel/lb_policy/grpclb/grpclb.cc index fa1ca6d127a..63e381d64c7 100644 --- a/src/core/ext/filters/client_channel/lb_policy/grpclb/grpclb.cc +++ b/src/core/ext/filters/client_channel/lb_policy/grpclb/grpclb.cc @@ -74,6 +74,7 @@ #include #include "src/core/ext/filters/client_channel/client_channel.h" +#include "src/core/ext/filters/client_channel/client_channel_factory.h" #include "src/core/ext/filters/client_channel/lb_policy/grpclb/client_load_reporting_filter.h" #include "src/core/ext/filters/client_channel/lb_policy/grpclb/grpclb.h" #include "src/core/ext/filters/client_channel/lb_policy/grpclb/grpclb_channel.h" @@ -130,6 +131,16 @@ class GrpcLb : public LoadBalancingPolicy { void UpdateLocked(const grpc_channel_args& args, grpc_json* lb_config) override; + bool PickLocked(PickState* pick, grpc_error** error) override; + void CancelPickLocked(PickState* pick, grpc_error* error) override; + void CancelMatchingPicksLocked(uint32_t initial_metadata_flags_mask, + uint32_t initial_metadata_flags_eq, + grpc_error* error) override; + void NotifyOnStateChangeLocked(grpc_connectivity_state* state, + grpc_closure* closure) override; + grpc_connectivity_state CheckConnectivityLocked( + grpc_error** connectivity_error) override; + void HandOffPendingPicksLocked(LoadBalancingPolicy* new_policy) override; void ExitIdleLocked() override; void ResetBackoffLocked() override; void FillChildRefsForChannelz( @@ -137,6 +148,31 @@ class GrpcLb : public LoadBalancingPolicy { channelz::ChildRefsList* child_channels) override; private: + /// Linked list of pending pick requests. It stores all information needed to + /// eventually call (Round Robin's) pick() on them. They mainly stay pending + /// waiting for the RR policy to be created. + /// + /// Note that when a pick is sent to the RR policy, we inject our own + /// on_complete callback, so that we can intercept the result before + /// invoking the original on_complete callback. This allows us to set the + /// LB token metadata and add client_stats to the call context. + /// See \a pending_pick_complete() for details. + struct PendingPick { + // The grpclb instance that created the wrapping. This instance is not + // owned; reference counts are untouched. It's used only for logging + // purposes. + GrpcLb* grpclb_policy; + // The original pick. + PickState* pick; + // Our on_complete closure and the original one. + grpc_closure on_complete; + grpc_closure* original_on_complete; + // Stats for client-side load reporting. + RefCountedPtr client_stats; + // Next pending pick. + PendingPick* next = nullptr; + }; + /// Contains a call to the LB server and all the data related to the call. class BalancerCallState : public InternallyRefCounted { public: @@ -212,80 +248,6 @@ class GrpcLb : public LoadBalancingPolicy { grpc_closure client_load_report_closure_; }; - class Serverlist : public RefCounted { - public: - // Takes ownership of serverlist. - explicit Serverlist(grpc_grpclb_serverlist* serverlist) - : serverlist_(serverlist) {} - - ~Serverlist() { grpc_grpclb_destroy_serverlist(serverlist_); } - - bool operator==(const Serverlist& other) const; - - const grpc_grpclb_serverlist* serverlist() const { return serverlist_; } - - // Returns a text representation suitable for logging. - UniquePtr AsText() const; - - // Extracts all non-drop entries into a ServerAddressList. - ServerAddressList GetServerAddressList() const; - - // Returns true if the serverlist contains at least one drop entry and - // no backend address entries. - bool ContainsAllDropEntries() const; - - // Returns the LB token to use for a drop, or null if the call - // should not be dropped. - // Intended to be called from picker, so calls will be externally - // synchronized. - const char* ShouldDrop(); - - private: - grpc_grpclb_serverlist* serverlist_; - size_t drop_index_ = 0; - }; - - class Picker : public SubchannelPicker { - public: - Picker(GrpcLb* parent, RefCountedPtr serverlist, - UniquePtr child_picker, - RefCountedPtr client_stats) - : parent_(parent), - serverlist_(std::move(serverlist)), - child_picker_(std::move(child_picker)), - client_stats_(std::move(client_stats)) {} - - PickResult Pick(PickState* pick, grpc_error** error) override; - - private: - // Storing the address for logging, but not holding a ref. - // DO NOT DEFERENCE! - GrpcLb* parent_; - - // Serverlist to be used for determining drops. - RefCountedPtr serverlist_; - - UniquePtr child_picker_; - RefCountedPtr client_stats_; - }; - - class Helper : public ChannelControlHelper { - public: - explicit Helper(RefCountedPtr parent) - : parent_(std::move(parent)) {} - - Subchannel* CreateSubchannel(const grpc_channel_args& args) override; - grpc_channel* CreateChannel(const char* target, - grpc_client_channel_type type, - const grpc_channel_args& args) override; - void UpdateState(grpc_connectivity_state state, grpc_error* state_error, - UniquePtr picker) override; - void RequestReresolution() override; - - private: - RefCountedPtr parent_; - }; - ~GrpcLb(); void ShutdownLocked() override; @@ -302,10 +264,24 @@ class GrpcLb : public LoadBalancingPolicy { static void OnBalancerChannelConnectivityChangedLocked(void* arg, grpc_error* error); + // Pending pick methods. + static void PendingPickSetMetadataAndContext(PendingPick* pp); + PendingPick* PendingPickCreate(PickState* pick); + void AddPendingPick(PendingPick* pp); + static void OnPendingPickComplete(void* arg, grpc_error* error); + // Methods for dealing with the RR policy. void CreateOrUpdateRoundRobinPolicyLocked(); grpc_channel_args* CreateRoundRobinPolicyArgsLocked(); void CreateRoundRobinPolicyLocked(Args args); + bool PickFromRoundRobinPolicyLocked(bool force_async, PendingPick* pp, + grpc_error** error); + void UpdateConnectivityStateFromRoundRobinPolicyLocked( + grpc_error* rr_state_error); + static void OnRoundRobinConnectivityChangedLocked(void* arg, + grpc_error* error); + static void OnRoundRobinRequestReresolutionLocked(void* arg, + grpc_error* error); // Who the client is trying to communicate with. const char* server_name_ = nullptr; @@ -316,6 +292,7 @@ class GrpcLb : public LoadBalancingPolicy { // Internal state. bool started_picking_ = false; bool shutting_down_ = false; + grpc_connectivity_state_tracker state_tracker_; // The channel for communicating with the LB server. grpc_channel* lb_channel_ = nullptr; @@ -344,7 +321,11 @@ class GrpcLb : public LoadBalancingPolicy { // The deserialized response from the balancer. May be nullptr until one // such response has arrived. - RefCountedPtr serverlist_; + grpc_grpclb_serverlist* serverlist_ = nullptr; + // Index into serverlist for next pick. + // If the server at this index is a drop, we return a drop. + // Otherwise, we delegate to the RR policy. + size_t serverlist_index_ = 0; // Timeout in milliseconds for before using fallback backend addresses. // 0 means not using fallback. @@ -356,65 +337,20 @@ class GrpcLb : public LoadBalancingPolicy { grpc_timer lb_fallback_timer_; grpc_closure lb_on_fallback_; + // Pending picks that are waiting on the RR policy's connectivity. + PendingPick* pending_picks_ = nullptr; + // The RR policy to use for the backends. OrphanablePtr rr_policy_; + grpc_connectivity_state rr_connectivity_state_; + grpc_closure on_rr_connectivity_changed_; + grpc_closure on_rr_request_reresolution_; }; // -// GrpcLb::Serverlist +// serverlist parsing code // -bool GrpcLb::Serverlist::operator==(const Serverlist& other) const { - return grpc_grpclb_serverlist_equals(serverlist_, other.serverlist_); -} - -void ParseServer(const grpc_grpclb_server* server, - grpc_resolved_address* addr) { - memset(addr, 0, sizeof(*addr)); - if (server->drop) return; - const uint16_t netorder_port = grpc_htons((uint16_t)server->port); - /* the addresses are given in binary format (a in(6)_addr struct) in - * server->ip_address.bytes. */ - const grpc_grpclb_ip_address* ip = &server->ip_address; - if (ip->size == 4) { - addr->len = static_cast(sizeof(grpc_sockaddr_in)); - grpc_sockaddr_in* addr4 = reinterpret_cast(&addr->addr); - addr4->sin_family = GRPC_AF_INET; - memcpy(&addr4->sin_addr, ip->bytes, ip->size); - addr4->sin_port = netorder_port; - } else if (ip->size == 16) { - addr->len = static_cast(sizeof(grpc_sockaddr_in6)); - grpc_sockaddr_in6* addr6 = (grpc_sockaddr_in6*)&addr->addr; - addr6->sin6_family = GRPC_AF_INET6; - memcpy(&addr6->sin6_addr, ip->bytes, ip->size); - addr6->sin6_port = netorder_port; - } -} - -UniquePtr GrpcLb::Serverlist::AsText() const { - gpr_strvec entries; - gpr_strvec_init(&entries); - for (size_t i = 0; i < serverlist_->num_servers; ++i) { - const auto* server = serverlist_->servers[i]; - char* ipport; - if (server->drop) { - ipport = gpr_strdup("(drop)"); - } else { - grpc_resolved_address addr; - ParseServer(server, &addr); - grpc_sockaddr_to_string(&ipport, &addr, false); - } - char* entry; - gpr_asprintf(&entry, " %" PRIuPTR ": %s token=%s\n", i, ipport, - server->load_balance_token); - gpr_free(ipport); - gpr_strvec_add(&entries, entry); - } - UniquePtr result(gpr_strvec_flatten(&entries, nullptr)); - gpr_strvec_destroy(&entries); - return result; -} - // vtable for LB token channel arg. void* lb_token_copy(void* token) { return token == nullptr @@ -457,12 +393,35 @@ bool IsServerValid(const grpc_grpclb_server* server, size_t idx, bool log) { return true; } -// Returns addresses extracted from the serverlist. -ServerAddressList GrpcLb::Serverlist::GetServerAddressList() const { +void ParseServer(const grpc_grpclb_server* server, + grpc_resolved_address* addr) { + memset(addr, 0, sizeof(*addr)); + if (server->drop) return; + const uint16_t netorder_port = grpc_htons((uint16_t)server->port); + /* the addresses are given in binary format (a in(6)_addr struct) in + * server->ip_address.bytes. */ + const grpc_grpclb_ip_address* ip = &server->ip_address; + if (ip->size == 4) { + addr->len = static_cast(sizeof(grpc_sockaddr_in)); + grpc_sockaddr_in* addr4 = reinterpret_cast(&addr->addr); + addr4->sin_family = GRPC_AF_INET; + memcpy(&addr4->sin_addr, ip->bytes, ip->size); + addr4->sin_port = netorder_port; + } else if (ip->size == 16) { + addr->len = static_cast(sizeof(grpc_sockaddr_in6)); + grpc_sockaddr_in6* addr6 = (grpc_sockaddr_in6*)&addr->addr; + addr6->sin6_family = GRPC_AF_INET6; + memcpy(&addr6->sin6_addr, ip->bytes, ip->size); + addr6->sin6_port = netorder_port; + } +} + +// Returns addresses extracted from \a serverlist. +ServerAddressList ProcessServerlist(const grpc_grpclb_serverlist* serverlist) { ServerAddressList addresses; - for (size_t i = 0; i < serverlist_->num_servers; ++i) { - const grpc_grpclb_server* server = serverlist_->servers[i]; - if (!IsServerValid(serverlist_->servers[i], i, false)) continue; + for (size_t i = 0; i < serverlist->num_servers; ++i) { + const grpc_grpclb_server* server = serverlist->servers[i]; + if (!IsServerValid(serverlist->servers[i], i, false)) continue; // Address processing. grpc_resolved_address addr; ParseServer(server, &addr); @@ -497,176 +456,6 @@ ServerAddressList GrpcLb::Serverlist::GetServerAddressList() const { return addresses; } -bool GrpcLb::Serverlist::ContainsAllDropEntries() const { - if (serverlist_->num_servers == 0) return false; - for (size_t i = 0; i < serverlist_->num_servers; ++i) { - if (!serverlist_->servers[i]->drop) return false; - } - return true; -} - -const char* GrpcLb::Serverlist::ShouldDrop() { - if (serverlist_->num_servers == 0) return nullptr; - grpc_grpclb_server* server = serverlist_->servers[drop_index_]; - drop_index_ = (drop_index_ + 1) % serverlist_->num_servers; - return server->drop ? server->load_balance_token : nullptr; -} - -// -// GrpcLb::Picker -// - -// Adds lb_token of selected subchannel (address) to the call's initial -// metadata. -grpc_error* AddLbTokenToInitialMetadata( - grpc_mdelem lb_token, grpc_linked_mdelem* lb_token_mdelem_storage, - grpc_metadata_batch* initial_metadata) { - GPR_ASSERT(lb_token_mdelem_storage != nullptr); - GPR_ASSERT(!GRPC_MDISNULL(lb_token)); - return grpc_metadata_batch_add_tail(initial_metadata, lb_token_mdelem_storage, - lb_token); -} - -// Destroy function used when embedding client stats in call context. -void DestroyClientStats(void* arg) { - static_cast(arg)->Unref(); -} - -GrpcLb::Picker::PickResult GrpcLb::Picker::Pick(PickState* pick, - grpc_error** error) { - // Check if we should drop the call. - const char* drop_token = serverlist_->ShouldDrop(); - if (drop_token != nullptr) { - // Update client load reporting stats to indicate the number of - // dropped calls. Note that we have to do this here instead of in - // the client_load_reporting filter, because we do not create a - // subchannel call (and therefore no client_load_reporting filter) - // for dropped calls. - if (client_stats_ != nullptr) { - client_stats_->AddCallDroppedLocked(drop_token); - } - return PICK_COMPLETE; - } - // Forward pick to child policy. - PickResult result = child_picker_->Pick(pick, error); - // If pick succeeded, add LB token to initial metadata. - if (result == PickResult::PICK_COMPLETE && - pick->connected_subchannel != nullptr) { - const grpc_arg* arg = grpc_channel_args_find( - pick->connected_subchannel->args(), GRPC_ARG_GRPCLB_ADDRESS_LB_TOKEN); - if (arg == nullptr) { - gpr_log(GPR_ERROR, - "[grpclb %p picker %p] No LB token for connected subchannel " - "pick %p", - parent_, this, pick); - abort(); - } - grpc_mdelem lb_token = {reinterpret_cast(arg->value.pointer.p)}; - AddLbTokenToInitialMetadata(GRPC_MDELEM_REF(lb_token), - &pick->lb_token_mdelem_storage, - pick->initial_metadata); - // Pass on client stats via context. Passes ownership of the reference. - if (client_stats_ != nullptr) { - pick->subchannel_call_context[GRPC_GRPCLB_CLIENT_STATS].value = - client_stats_->Ref().release(); - pick->subchannel_call_context[GRPC_GRPCLB_CLIENT_STATS].destroy = - DestroyClientStats; - } - } - return result; -} - -// -// GrpcLb::Helper -// - -Subchannel* GrpcLb::Helper::CreateSubchannel(const grpc_channel_args& args) { - if (parent_->shutting_down_) return nullptr; - return parent_->channel_control_helper()->CreateSubchannel(args); -} - -grpc_channel* GrpcLb::Helper::CreateChannel(const char* target, - grpc_client_channel_type type, - const grpc_channel_args& args) { - if (parent_->shutting_down_) return nullptr; - return parent_->channel_control_helper()->CreateChannel(target, type, args); -} - -void GrpcLb::Helper::UpdateState(grpc_connectivity_state state, - grpc_error* state_error, - UniquePtr picker) { - if (parent_->shutting_down_) { - GRPC_ERROR_UNREF(state_error); - return; - } - // There are three cases to consider here: - // 1. We're in fallback mode. In this case, we're always going to use - // RR's result, so we pass its picker through as-is. - // 2. The serverlist contains only drop entries. In this case, we - // want to use our own picker so that we can return the drops. - // 3. Not in fallback mode and serverlist is not all drops (i.e., it - // may be empty or contain at least one backend address). There are - // two sub-cases: - // a. RR is reporting state READY. In this case, we wrap RR's - // picker in our own, so that we can handle drops and LB token - // metadata for each pick. - // b. RR is reporting a state other than READY. In this case, we - // don't want to use our own picker, because we don't want to - // process drops for picks that yield a QUEUE result; this would - // result in dropping too many calls, since we will see the - // queued picks multiple times, and we'd consider each one a - // separate call for the drop calculation. - // - // Cases 1 and 3b: return picker from RR as-is. - if (parent_->serverlist_ == nullptr || - (!parent_->serverlist_->ContainsAllDropEntries() && - state != GRPC_CHANNEL_READY)) { - if (grpc_lb_glb_trace.enabled()) { - gpr_log(GPR_INFO, - "[grpclb %p helper %p] state=%s passing RR picker %p as-is", - parent_.get(), this, grpc_connectivity_state_name(state), - picker.get()); - } - parent_->channel_control_helper()->UpdateState(state, state_error, - std::move(picker)); - return; - } - // Cases 2 and 3a: wrap picker from RR in our own picker. - if (grpc_lb_glb_trace.enabled()) { - gpr_log(GPR_INFO, "[grpclb %p helper %p] state=%s wrapping RR picker %p", - parent_.get(), this, grpc_connectivity_state_name(state), - picker.get()); - } - RefCountedPtr client_stats; - if (parent_->lb_calld_ != nullptr && - parent_->lb_calld_->client_stats() != nullptr) { - client_stats = parent_->lb_calld_->client_stats()->Ref(); - } - parent_->channel_control_helper()->UpdateState( - state, state_error, - UniquePtr( - New(parent_.get(), parent_->serverlist_, std::move(picker), - std::move(client_stats)))); -} - -void GrpcLb::Helper::RequestReresolution() { - if (parent_->shutting_down_) return; - if (grpc_lb_glb_trace.enabled()) { - gpr_log(GPR_INFO, - "[grpclb %p] Re-resolution requested from the internal RR policy " - "(%p).", - parent_.get(), parent_->rr_policy_.get()); - } - // If we are talking to a balancer, we expect to get updated addresses - // from the balancer, so we can ignore the re-resolution request from - // the RR policy. Otherwise, pass the re-resolution request up to the - // channel. - if (parent_->lb_calld_ == nullptr || - !parent_->lb_calld_->seen_initial_response()) { - parent_->channel_control_helper()->RequestReresolution(); - } -} - // // GrpcLb::BalancerCallState // @@ -965,20 +754,27 @@ void GrpcLb::BalancerCallState::OnBalancerMessageReceivedLocked( response_slice)) != nullptr) { // Have seen initial response, look for serverlist. GPR_ASSERT(lb_calld->lb_call_ != nullptr); - auto serverlist_wrapper = MakeRefCounted(serverlist); if (grpc_lb_glb_trace.enabled()) { - UniquePtr serverlist_text = serverlist_wrapper->AsText(); gpr_log(GPR_INFO, "[grpclb %p] lb_calld=%p: Serverlist with %" PRIuPTR - " servers received:\n%s", - grpclb_policy, lb_calld, serverlist->num_servers, - serverlist_text.get()); + " servers received", + grpclb_policy, lb_calld, serverlist->num_servers); + for (size_t i = 0; i < serverlist->num_servers; ++i) { + grpc_resolved_address addr; + ParseServer(serverlist->servers[i], &addr); + char* ipport; + grpc_sockaddr_to_string(&ipport, &addr, false); + gpr_log(GPR_INFO, + "[grpclb %p] lb_calld=%p: Serverlist[%" PRIuPTR "]: %s", + grpclb_policy, lb_calld, i, ipport); + gpr_free(ipport); + } } // Start sending client load report only after we start using the // serverlist returned from the current LB call. if (lb_calld->client_stats_report_interval_ > 0 && lb_calld->client_stats_ == nullptr) { - lb_calld->client_stats_ = MakeRefCounted(); + lb_calld->client_stats_.reset(New()); // TODO(roth): We currently track this ref manually. Once the // ClosureRef API is ready, we should pass the RefCountedPtr<> along // with the callback. @@ -987,16 +783,19 @@ void GrpcLb::BalancerCallState::OnBalancerMessageReceivedLocked( lb_calld->ScheduleNextClientLoadReportLocked(); } // Check if the serverlist differs from the previous one. - if (grpclb_policy->serverlist_ != nullptr && - *grpclb_policy->serverlist_ == *serverlist_wrapper) { + if (grpc_grpclb_serverlist_equals(grpclb_policy->serverlist_, serverlist)) { if (grpc_lb_glb_trace.enabled()) { gpr_log(GPR_INFO, "[grpclb %p] lb_calld=%p: Incoming server list identical to " "current, ignoring.", grpclb_policy, lb_calld); } + grpc_grpclb_destroy_serverlist(serverlist); } else { // New serverlist. - if (grpclb_policy->serverlist_ == nullptr) { + if (grpclb_policy->serverlist_ != nullptr) { + // Dispose of the old serverlist. + grpc_grpclb_destroy_serverlist(grpclb_policy->serverlist_); + } else { // Dispose of the fallback. grpclb_policy->fallback_backend_addresses_.reset(); if (grpclb_policy->fallback_timer_callback_pending_) { @@ -1006,7 +805,8 @@ void GrpcLb::BalancerCallState::OnBalancerMessageReceivedLocked( // Update the serverlist in the GrpcLb instance. This serverlist // instance will be destroyed either upon the next update or when the // GrpcLb instance is destroyed. - grpclb_policy->serverlist_ = std::move(serverlist_wrapper); + grpclb_policy->serverlist_ = serverlist; + grpclb_policy->serverlist_index_ = 0; grpclb_policy->CreateOrUpdateRoundRobinPolicyLocked(); } } else { @@ -1053,13 +853,13 @@ void GrpcLb::BalancerCallState::OnBalancerStatusReceivedLocked( lb_calld->lb_call_, grpc_error_string(error)); gpr_free(status_details); } + grpclb_policy->TryReresolutionLocked(&grpc_lb_glb_trace, GRPC_ERROR_NONE); // If this lb_calld is still in use, this call ended because of a failure so // we want to retry connecting. Otherwise, we have deliberately ended this // call and no further action is required. if (lb_calld == grpclb_policy->lb_calld_.get()) { grpclb_policy->lb_calld_.reset(); GPR_ASSERT(!grpclb_policy->shutting_down_); - grpclb_policy->channel_control_helper()->RequestReresolution(); if (lb_calld->seen_initial_response_) { // If we lose connection to the LB server, reset the backoff and restart // the LB call immediately. @@ -1191,6 +991,13 @@ GrpcLb::GrpcLb(LoadBalancingPolicy::Args args) GRPC_CLOSURE_INIT(&lb_channel_on_connectivity_changed_, &GrpcLb::OnBalancerChannelConnectivityChangedLocked, this, grpc_combiner_scheduler(args.combiner)); + GRPC_CLOSURE_INIT(&on_rr_connectivity_changed_, + &GrpcLb::OnRoundRobinConnectivityChangedLocked, this, + grpc_combiner_scheduler(args.combiner)); + GRPC_CLOSURE_INIT(&on_rr_request_reresolution_, + &GrpcLb::OnRoundRobinRequestReresolutionLocked, this, + grpc_combiner_scheduler(args.combiner)); + grpc_connectivity_state_init(&state_tracker_, GRPC_CHANNEL_IDLE, "grpclb"); // Record server name. const grpc_arg* arg = grpc_channel_args_find(args.args, GRPC_ARG_SERVER_URI); const char* server_uri = grpc_channel_arg_get_string(arg); @@ -1213,18 +1020,20 @@ GrpcLb::GrpcLb(LoadBalancingPolicy::Args args) arg, {GRPC_GRPCLB_DEFAULT_FALLBACK_TIMEOUT_MS, 0, INT_MAX}); // Process channel args. ProcessChannelArgsLocked(*args.args); - // Initialize channel with a picker that will start us connecting. - channel_control_helper()->UpdateState( - GRPC_CHANNEL_IDLE, GRPC_ERROR_NONE, - UniquePtr(New(Ref()))); } GrpcLb::~GrpcLb() { + GPR_ASSERT(pending_picks_ == nullptr); gpr_free((void*)server_name_); grpc_channel_args_destroy(args_); + grpc_connectivity_state_destroy(&state_tracker_); + if (serverlist_ != nullptr) { + grpc_grpclb_destroy_serverlist(serverlist_); + } } void GrpcLb::ShutdownLocked() { + grpc_error* error = GRPC_ERROR_CREATE_FROM_STATIC_STRING("Channel shutdown"); shutting_down_ = true; lb_calld_.reset(); if (retry_timer_callback_pending_) { @@ -1234,6 +1043,7 @@ void GrpcLb::ShutdownLocked() { grpc_timer_cancel(&lb_fallback_timer_); } rr_policy_.reset(); + TryReresolutionLocked(&grpc_lb_glb_trace, GRPC_ERROR_CANCELLED); // We destroy the LB channel here instead of in our destructor because // destroying the channel triggers a last callback to // OnBalancerChannelConnectivityChangedLocked(), and we need to be @@ -1243,12 +1053,109 @@ void GrpcLb::ShutdownLocked() { lb_channel_ = nullptr; gpr_atm_no_barrier_store(&lb_channel_uuid_, 0); } + grpc_connectivity_state_set(&state_tracker_, GRPC_CHANNEL_SHUTDOWN, + GRPC_ERROR_REF(error), "grpclb_shutdown"); + // Clear pending picks. + PendingPick* pp; + while ((pp = pending_picks_) != nullptr) { + pending_picks_ = pp->next; + pp->pick->connected_subchannel.reset(); + // Note: pp is deleted in this callback. + GRPC_CLOSURE_SCHED(&pp->on_complete, GRPC_ERROR_REF(error)); + } + GRPC_ERROR_UNREF(error); } // // public methods // +void GrpcLb::HandOffPendingPicksLocked(LoadBalancingPolicy* new_policy) { + PendingPick* pp; + while ((pp = pending_picks_) != nullptr) { + pending_picks_ = pp->next; + pp->pick->on_complete = pp->original_on_complete; + grpc_error* error = GRPC_ERROR_NONE; + if (new_policy->PickLocked(pp->pick, &error)) { + // Synchronous return; schedule closure. + GRPC_CLOSURE_SCHED(pp->pick->on_complete, error); + } + Delete(pp); + } +} + +// Cancel a specific pending pick. +// +// A grpclb pick progresses as follows: +// - If there's a Round Robin policy (rr_policy_) available, it'll be +// handed over to the RR policy (in CreateRoundRobinPolicyLocked()). From +// that point onwards, it'll be RR's responsibility. For cancellations, that +// implies the pick needs also be cancelled by the RR instance. +// - Otherwise, without an RR instance, picks stay pending at this policy's +// level (grpclb), inside the pending_picks_ list. To cancel these, +// we invoke the completion closure and set the pick's connected +// subchannel to nullptr right here. +void GrpcLb::CancelPickLocked(PickState* pick, grpc_error* error) { + PendingPick* pp = pending_picks_; + pending_picks_ = nullptr; + while (pp != nullptr) { + PendingPick* next = pp->next; + if (pp->pick == pick) { + pick->connected_subchannel.reset(); + // Note: pp is deleted in this callback. + GRPC_CLOSURE_SCHED(&pp->on_complete, + GRPC_ERROR_CREATE_REFERENCING_FROM_STATIC_STRING( + "Pick Cancelled", &error, 1)); + } else { + pp->next = pending_picks_; + pending_picks_ = pp; + } + pp = next; + } + if (rr_policy_ != nullptr) { + rr_policy_->CancelPickLocked(pick, GRPC_ERROR_REF(error)); + } + GRPC_ERROR_UNREF(error); +} + +// Cancel all pending picks. +// +// A grpclb pick progresses as follows: +// - If there's a Round Robin policy (rr_policy_) available, it'll be +// handed over to the RR policy (in CreateRoundRobinPolicyLocked()). From +// that point onwards, it'll be RR's responsibility. For cancellations, that +// implies the pick needs also be cancelled by the RR instance. +// - Otherwise, without an RR instance, picks stay pending at this policy's +// level (grpclb), inside the pending_picks_ list. To cancel these, +// we invoke the completion closure and set the pick's connected +// subchannel to nullptr right here. +void GrpcLb::CancelMatchingPicksLocked(uint32_t initial_metadata_flags_mask, + uint32_t initial_metadata_flags_eq, + grpc_error* error) { + PendingPick* pp = pending_picks_; + pending_picks_ = nullptr; + while (pp != nullptr) { + PendingPick* next = pp->next; + if ((*pp->pick->initial_metadata_flags & initial_metadata_flags_mask) == + initial_metadata_flags_eq) { + // Note: pp is deleted in this callback. + GRPC_CLOSURE_SCHED(&pp->on_complete, + GRPC_ERROR_CREATE_REFERENCING_FROM_STATIC_STRING( + "Pick Cancelled", &error, 1)); + } else { + pp->next = pending_picks_; + pending_picks_ = pp; + } + pp = next; + } + if (rr_policy_ != nullptr) { + rr_policy_->CancelMatchingPicksLocked(initial_metadata_flags_mask, + initial_metadata_flags_eq, + GRPC_ERROR_REF(error)); + } + GRPC_ERROR_UNREF(error); +} + void GrpcLb::ExitIdleLocked() { if (!started_picking_) { StartPickingLocked(); @@ -1264,6 +1171,37 @@ void GrpcLb::ResetBackoffLocked() { } } +bool GrpcLb::PickLocked(PickState* pick, grpc_error** error) { + PendingPick* pp = PendingPickCreate(pick); + bool pick_done = false; + if (rr_policy_ != nullptr) { + if (grpc_lb_glb_trace.enabled()) { + gpr_log(GPR_INFO, "[grpclb %p] about to PICK from RR %p", this, + rr_policy_.get()); + } + pick_done = + PickFromRoundRobinPolicyLocked(false /* force_async */, pp, error); + } else { // rr_policy_ == NULL + if (pick->on_complete == nullptr) { + *error = GRPC_ERROR_CREATE_FROM_STATIC_STRING( + "No pick result available but synchronous result required."); + pick_done = true; + } else { + if (grpc_lb_glb_trace.enabled()) { + gpr_log(GPR_INFO, + "[grpclb %p] No RR policy. Adding to grpclb's pending picks", + this); + } + AddPendingPick(pp); + if (!started_picking_) { + StartPickingLocked(); + } + pick_done = false; + } + } + return pick_done; +} + void GrpcLb::FillChildRefsForChannelz( channelz::ChildRefsList* child_subchannels, channelz::ChildRefsList* child_channels) { @@ -1277,6 +1215,17 @@ void GrpcLb::FillChildRefsForChannelz( } } +grpc_connectivity_state GrpcLb::CheckConnectivityLocked( + grpc_error** connectivity_error) { + return grpc_connectivity_state_get(&state_tracker_, connectivity_error); +} + +void GrpcLb::NotifyOnStateChangeLocked(grpc_connectivity_state* current, + grpc_closure* notify) { + grpc_connectivity_state_notify_on_state_change(&state_tracker_, current, + notify); +} + // Returns the backend addresses extracted from the given addresses. UniquePtr ExtractBackendAddresses( const ServerAddressList& addresses) { @@ -1322,8 +1271,9 @@ void GrpcLb::ProcessChannelArgsLocked(const grpc_channel_args& args) { if (lb_channel_ == nullptr) { char* uri_str; gpr_asprintf(&uri_str, "fake:///%s", server_name_); - lb_channel_ = channel_control_helper()->CreateChannel( - uri_str, GRPC_CLIENT_CHANNEL_TYPE_LOAD_BALANCING, *lb_channel_args); + lb_channel_ = grpc_client_channel_factory_create_channel( + client_channel_factory(), uri_str, + GRPC_CLIENT_CHANNEL_TYPE_LOAD_BALANCING, lb_channel_args); GPR_ASSERT(lb_channel_ != nullptr); grpc_core::channelz::ChannelNode* channel_node = grpc_channel_get_channelz_node(lb_channel_); @@ -1504,10 +1454,143 @@ void GrpcLb::OnBalancerChannelConnectivityChangedLocked(void* arg, } } +// +// PendingPick +// + +// Adds lb_token of selected subchannel (address) to the call's initial +// metadata. +grpc_error* AddLbTokenToInitialMetadata( + grpc_mdelem lb_token, grpc_linked_mdelem* lb_token_mdelem_storage, + grpc_metadata_batch* initial_metadata) { + GPR_ASSERT(lb_token_mdelem_storage != nullptr); + GPR_ASSERT(!GRPC_MDISNULL(lb_token)); + return grpc_metadata_batch_add_tail(initial_metadata, lb_token_mdelem_storage, + lb_token); +} + +// Destroy function used when embedding client stats in call context. +void DestroyClientStats(void* arg) { + static_cast(arg)->Unref(); +} + +void GrpcLb::PendingPickSetMetadataAndContext(PendingPick* pp) { + // If connected_subchannel is nullptr, no pick has been made by the RR + // policy (e.g., all addresses failed to connect). There won't be any + // LB token available. + if (pp->pick->connected_subchannel != nullptr) { + const grpc_arg* arg = + grpc_channel_args_find(pp->pick->connected_subchannel->args(), + GRPC_ARG_GRPCLB_ADDRESS_LB_TOKEN); + if (arg != nullptr) { + grpc_mdelem lb_token = { + reinterpret_cast(arg->value.pointer.p)}; + AddLbTokenToInitialMetadata(GRPC_MDELEM_REF(lb_token), + &pp->pick->lb_token_mdelem_storage, + pp->pick->initial_metadata); + } else { + gpr_log(GPR_ERROR, + "[grpclb %p] No LB token for connected subchannel pick %p", + pp->grpclb_policy, pp->pick); + abort(); + } + // Pass on client stats via context. Passes ownership of the reference. + if (pp->client_stats != nullptr) { + pp->pick->subchannel_call_context[GRPC_GRPCLB_CLIENT_STATS].value = + pp->client_stats.release(); + pp->pick->subchannel_call_context[GRPC_GRPCLB_CLIENT_STATS].destroy = + DestroyClientStats; + } + } else { + pp->client_stats.reset(); + } +} + +/* The \a on_complete closure passed as part of the pick requires keeping a + * reference to its associated round robin instance. We wrap this closure in + * order to unref the round robin instance upon its invocation */ +void GrpcLb::OnPendingPickComplete(void* arg, grpc_error* error) { + PendingPick* pp = static_cast(arg); + PendingPickSetMetadataAndContext(pp); + GRPC_CLOSURE_SCHED(pp->original_on_complete, GRPC_ERROR_REF(error)); + Delete(pp); +} + +GrpcLb::PendingPick* GrpcLb::PendingPickCreate(PickState* pick) { + PendingPick* pp = New(); + pp->grpclb_policy = this; + pp->pick = pick; + GRPC_CLOSURE_INIT(&pp->on_complete, &GrpcLb::OnPendingPickComplete, pp, + grpc_schedule_on_exec_ctx); + pp->original_on_complete = pick->on_complete; + pick->on_complete = &pp->on_complete; + return pp; +} + +void GrpcLb::AddPendingPick(PendingPick* pp) { + pp->next = pending_picks_; + pending_picks_ = pp; +} + // // code for interacting with the RR policy // +// Performs a pick over \a rr_policy_. Given that a pick can return +// immediately (ignoring its completion callback), we need to perform the +// cleanups this callback would otherwise be responsible for. +// If \a force_async is true, then we will manually schedule the +// completion callback even if the pick is available immediately. +bool GrpcLb::PickFromRoundRobinPolicyLocked(bool force_async, PendingPick* pp, + grpc_error** error) { + // Check for drops if we are not using fallback backend addresses. + if (serverlist_ != nullptr && serverlist_->num_servers > 0) { + // Look at the index into the serverlist to see if we should drop this call. + grpc_grpclb_server* server = serverlist_->servers[serverlist_index_++]; + if (serverlist_index_ == serverlist_->num_servers) { + serverlist_index_ = 0; // Wrap-around. + } + if (server->drop) { + // Update client load reporting stats to indicate the number of + // dropped calls. Note that we have to do this here instead of in + // the client_load_reporting filter, because we do not create a + // subchannel call (and therefore no client_load_reporting filter) + // for dropped calls. + if (lb_calld_ != nullptr && lb_calld_->client_stats() != nullptr) { + lb_calld_->client_stats()->AddCallDroppedLocked( + server->load_balance_token); + } + if (force_async) { + GRPC_CLOSURE_SCHED(pp->original_on_complete, GRPC_ERROR_NONE); + Delete(pp); + return false; + } + Delete(pp); + return true; + } + } + // Set client_stats. + if (lb_calld_ != nullptr && lb_calld_->client_stats() != nullptr) { + pp->client_stats = lb_calld_->client_stats()->Ref(); + } + // Pick via the RR policy. + bool pick_done = rr_policy_->PickLocked(pp->pick, error); + if (pick_done) { + PendingPickSetMetadataAndContext(pp); + if (force_async) { + GRPC_CLOSURE_SCHED(pp->original_on_complete, *error); + *error = GRPC_ERROR_NONE; + pick_done = false; + } + Delete(pp); + } + // else, the pending pick will be registered and taken care of by the + // pending pick list inside the RR policy. Eventually, + // OnPendingPickComplete() will be called, which will (among other + // things) add the LB token to the call's initial metadata. + return pick_done; +} + void GrpcLb::CreateRoundRobinPolicyLocked(Args args) { GPR_ASSERT(rr_policy_ == nullptr); rr_policy_ = LoadBalancingPolicyRegistry::CreateLoadBalancingPolicy( @@ -1521,12 +1604,40 @@ void GrpcLb::CreateRoundRobinPolicyLocked(Args args) { gpr_log(GPR_INFO, "[grpclb %p] Created new RR policy %p", this, rr_policy_.get()); } + // TODO(roth): We currently track this ref manually. Once the new + // ClosureRef API is done, pass the RefCountedPtr<> along with the closure. + auto self = Ref(DEBUG_LOCATION, "on_rr_reresolution_requested"); + self.release(); + rr_policy_->SetReresolutionClosureLocked(&on_rr_request_reresolution_); + grpc_error* rr_state_error = nullptr; + rr_connectivity_state_ = rr_policy_->CheckConnectivityLocked(&rr_state_error); + // Connectivity state is a function of the RR policy updated/created. + UpdateConnectivityStateFromRoundRobinPolicyLocked(rr_state_error); // Add the gRPC LB's interested_parties pollset_set to that of the newly // created RR policy. This will make the RR policy progress upon activity on // gRPC LB, which in turn is tied to the application's call. grpc_pollset_set_add_pollset_set(rr_policy_->interested_parties(), interested_parties()); + // Subscribe to changes to the connectivity of the new RR. + // TODO(roth): We currently track this ref manually. Once the new + // ClosureRef API is done, pass the RefCountedPtr<> along with the closure. + self = Ref(DEBUG_LOCATION, "on_rr_connectivity_changed"); + self.release(); + rr_policy_->NotifyOnStateChangeLocked(&rr_connectivity_state_, + &on_rr_connectivity_changed_); rr_policy_->ExitIdleLocked(); + // Send pending picks to RR policy. + PendingPick* pp; + while ((pp = pending_picks_)) { + pending_picks_ = pp->next; + if (grpc_lb_glb_trace.enabled()) { + gpr_log(GPR_INFO, + "[grpclb %p] Pending pick about to (async) PICK from RR %p", this, + rr_policy_.get()); + } + grpc_error* error = GRPC_ERROR_NONE; + PickFromRoundRobinPolicyLocked(true /* force_async */, pp, &error); + } } grpc_channel_args* GrpcLb::CreateRoundRobinPolicyArgsLocked() { @@ -1534,7 +1645,7 @@ grpc_channel_args* GrpcLb::CreateRoundRobinPolicyArgsLocked() { ServerAddressList* addresses = &tmp_addresses; bool is_backend_from_grpclb_load_balancer = false; if (serverlist_ != nullptr) { - tmp_addresses = serverlist_->GetServerAddressList(); + tmp_addresses = ProcessServerlist(serverlist_); is_backend_from_grpclb_load_balancer = true; } else { // If CreateOrUpdateRoundRobinPolicyLocked() is invoked when we haven't @@ -1583,14 +1694,110 @@ void GrpcLb::CreateOrUpdateRoundRobinPolicyLocked() { } else { LoadBalancingPolicy::Args lb_policy_args; lb_policy_args.combiner = combiner(); + lb_policy_args.client_channel_factory = client_channel_factory(); lb_policy_args.args = args; - lb_policy_args.channel_control_helper = - UniquePtr(New(Ref())); + lb_policy_args.subchannel_pool = subchannel_pool()->Ref(); CreateRoundRobinPolicyLocked(std::move(lb_policy_args)); } grpc_channel_args_destroy(args); } +void GrpcLb::OnRoundRobinRequestReresolutionLocked(void* arg, + grpc_error* error) { + GrpcLb* grpclb_policy = static_cast(arg); + if (grpclb_policy->shutting_down_ || error != GRPC_ERROR_NONE) { + grpclb_policy->Unref(DEBUG_LOCATION, "on_rr_reresolution_requested"); + return; + } + if (grpc_lb_glb_trace.enabled()) { + gpr_log( + GPR_INFO, + "[grpclb %p] Re-resolution requested from the internal RR policy (%p).", + grpclb_policy, grpclb_policy->rr_policy_.get()); + } + // If we are talking to a balancer, we expect to get updated addresses form + // the balancer, so we can ignore the re-resolution request from the RR + // policy. Otherwise, handle the re-resolution request using the + // grpclb policy's original re-resolution closure. + if (grpclb_policy->lb_calld_ == nullptr || + !grpclb_policy->lb_calld_->seen_initial_response()) { + grpclb_policy->TryReresolutionLocked(&grpc_lb_glb_trace, GRPC_ERROR_NONE); + } + // Give back the wrapper closure to the RR policy. + grpclb_policy->rr_policy_->SetReresolutionClosureLocked( + &grpclb_policy->on_rr_request_reresolution_); +} + +void GrpcLb::UpdateConnectivityStateFromRoundRobinPolicyLocked( + grpc_error* rr_state_error) { + const grpc_connectivity_state curr_glb_state = + grpc_connectivity_state_check(&state_tracker_); + /* The new connectivity status is a function of the previous one and the new + * input coming from the status of the RR policy. + * + * current state (grpclb's) + * | + * v || I | C | R | TF | SD | <- new state (RR's) + * ===++====+=====+=====+======+======+ + * I || I | C | R | [I] | [I] | + * ---++----+-----+-----+------+------+ + * C || I | C | R | [C] | [C] | + * ---++----+-----+-----+------+------+ + * R || I | C | R | [R] | [R] | + * ---++----+-----+-----+------+------+ + * TF || I | C | R | [TF] | [TF] | + * ---++----+-----+-----+------+------+ + * SD || NA | NA | NA | NA | NA | (*) + * ---++----+-----+-----+------+------+ + * + * A [STATE] indicates that the old RR policy is kept. In those cases, STATE + * is the current state of grpclb, which is left untouched. + * + * In summary, if the new state is TRANSIENT_FAILURE or SHUTDOWN, stick to + * the previous RR instance. + * + * Note that the status is never updated to SHUTDOWN as a result of calling + * this function. Only glb_shutdown() has the power to set that state. + * + * (*) This function mustn't be called during shutting down. */ + GPR_ASSERT(curr_glb_state != GRPC_CHANNEL_SHUTDOWN); + switch (rr_connectivity_state_) { + case GRPC_CHANNEL_TRANSIENT_FAILURE: + case GRPC_CHANNEL_SHUTDOWN: + GPR_ASSERT(rr_state_error != GRPC_ERROR_NONE); + break; + case GRPC_CHANNEL_IDLE: + case GRPC_CHANNEL_CONNECTING: + case GRPC_CHANNEL_READY: + GPR_ASSERT(rr_state_error == GRPC_ERROR_NONE); + } + if (grpc_lb_glb_trace.enabled()) { + gpr_log( + GPR_INFO, + "[grpclb %p] Setting grpclb's state to %s from new RR policy %p state.", + this, grpc_connectivity_state_name(rr_connectivity_state_), + rr_policy_.get()); + } + grpc_connectivity_state_set(&state_tracker_, rr_connectivity_state_, + rr_state_error, + "update_lb_connectivity_status_locked"); +} + +void GrpcLb::OnRoundRobinConnectivityChangedLocked(void* arg, + grpc_error* error) { + GrpcLb* grpclb_policy = static_cast(arg); + if (grpclb_policy->shutting_down_) { + grpclb_policy->Unref(DEBUG_LOCATION, "on_rr_connectivity_changed"); + return; + } + grpclb_policy->UpdateConnectivityStateFromRoundRobinPolicyLocked( + GRPC_ERROR_REF(error)); + // Resubscribe. Reuse the "on_rr_connectivity_changed" ref. + grpclb_policy->rr_policy_->NotifyOnStateChangeLocked( + &grpclb_policy->rr_connectivity_state_, + &grpclb_policy->on_rr_connectivity_changed_); +} + // // factory // diff --git a/src/core/ext/filters/client_channel/lb_policy/grpclb/grpclb_client_stats.cc b/src/core/ext/filters/client_channel/lb_policy/grpclb/grpclb_client_stats.cc index 1c7ed871d74..087cd8f276e 100644 --- a/src/core/ext/filters/client_channel/lb_policy/grpclb/grpclb_client_stats.cc +++ b/src/core/ext/filters/client_channel/lb_policy/grpclb/grpclb_client_stats.cc @@ -43,7 +43,7 @@ void GrpcLbClientStats::AddCallFinished( } } -void GrpcLbClientStats::AddCallDroppedLocked(const char* token) { +void GrpcLbClientStats::AddCallDroppedLocked(char* token) { // Increment num_calls_started and num_calls_finished. gpr_atm_full_fetch_add(&num_calls_started_, (gpr_atm)1); gpr_atm_full_fetch_add(&num_calls_finished_, (gpr_atm)1); diff --git a/src/core/ext/filters/client_channel/lb_policy/grpclb/grpclb_client_stats.h b/src/core/ext/filters/client_channel/lb_policy/grpclb/grpclb_client_stats.h index 45ca40942ca..18ab2c94529 100644 --- a/src/core/ext/filters/client_channel/lb_policy/grpclb/grpclb_client_stats.h +++ b/src/core/ext/filters/client_channel/lb_policy/grpclb/grpclb_client_stats.h @@ -48,7 +48,7 @@ class GrpcLbClientStats : public RefCounted { bool finished_known_received); // This method is not thread-safe; caller must synchronize. - void AddCallDroppedLocked(const char* token); + void AddCallDroppedLocked(char* token); // This method is not thread-safe; caller must synchronize. void GetLocked(int64_t* num_calls_started, int64_t* num_calls_finished, diff --git a/src/core/ext/filters/client_channel/lb_policy/pick_first/pick_first.cc b/src/core/ext/filters/client_channel/lb_policy/pick_first/pick_first.cc index bf1c5bd7914..dc716a6adac 100644 --- a/src/core/ext/filters/client_channel/lb_policy/pick_first/pick_first.cc +++ b/src/core/ext/filters/client_channel/lb_policy/pick_first/pick_first.cc @@ -52,6 +52,16 @@ class PickFirst : public LoadBalancingPolicy { void UpdateLocked(const grpc_channel_args& args, grpc_json* lb_config) override; + bool PickLocked(PickState* pick, grpc_error** error) override; + void CancelPickLocked(PickState* pick, grpc_error* error) override; + void CancelMatchingPicksLocked(uint32_t initial_metadata_flags_mask, + uint32_t initial_metadata_flags_eq, + grpc_error* error) override; + void NotifyOnStateChangeLocked(grpc_connectivity_state* state, + grpc_closure* closure) override; + grpc_connectivity_state CheckConnectivityLocked( + grpc_error** connectivity_error) override; + void HandOffPendingPicksLocked(LoadBalancingPolicy* new_policy) override; void ExitIdleLocked() override; void ResetBackoffLocked() override; void FillChildRefsForChannelz(channelz::ChildRefsList* child_subchannels, @@ -89,9 +99,10 @@ class PickFirst : public LoadBalancingPolicy { PickFirstSubchannelList(PickFirst* policy, TraceFlag* tracer, const ServerAddressList& addresses, grpc_combiner* combiner, + grpc_client_channel_factory* client_channel_factory, const grpc_channel_args& args) : SubchannelList(policy, tracer, addresses, combiner, - policy->channel_control_helper(), args) { + client_channel_factory, args) { // Need to maintain a ref to the LB policy as long as we maintain // any references to subchannels, since the subchannels' // pollset_sets will include the LB policy's pollset_set. @@ -104,20 +115,6 @@ class PickFirst : public LoadBalancingPolicy { } }; - class Picker : public SubchannelPicker { - public: - explicit Picker(RefCountedPtr connected_subchannel) - : connected_subchannel_(std::move(connected_subchannel)) {} - - PickResult Pick(PickState* pick, grpc_error** error) override { - pick->connected_subchannel = connected_subchannel_; - return PICK_COMPLETE; - } - - private: - RefCountedPtr connected_subchannel_; - }; - // Helper class to ensure that any function that modifies the child refs // data structures will update the channelz snapshot data structures before // returning. @@ -145,6 +142,10 @@ class PickFirst : public LoadBalancingPolicy { bool started_picking_ = false; // Are we shut down? bool shutdown_ = false; + // List of picks that are waiting on connectivity. + PickState* pending_picks_ = nullptr; + // Our connectivity state tracker. + grpc_connectivity_state_tracker state_tracker_; /// Lock and data used to capture snapshots of this channels child /// channels and subchannels. This data is consumed by channelz. @@ -154,15 +155,13 @@ class PickFirst : public LoadBalancingPolicy { }; PickFirst::PickFirst(Args args) : LoadBalancingPolicy(std::move(args)) { + GPR_ASSERT(args.client_channel_factory != nullptr); gpr_mu_init(&child_refs_mu_); + grpc_connectivity_state_init(&state_tracker_, GRPC_CHANNEL_IDLE, + "pick_first"); if (grpc_lb_pick_first_trace.enabled()) { gpr_log(GPR_INFO, "Pick First %p created.", this); } - // Initialize channel with a picker that will start us connecting upon - // the first pick. - channel_control_helper()->UpdateState( - GRPC_CHANNEL_IDLE, GRPC_ERROR_NONE, - UniquePtr(New(Ref()))); UpdateLocked(*args.args, args.lb_config); } @@ -173,16 +172,81 @@ PickFirst::~PickFirst() { gpr_mu_destroy(&child_refs_mu_); GPR_ASSERT(subchannel_list_ == nullptr); GPR_ASSERT(latest_pending_subchannel_list_ == nullptr); + GPR_ASSERT(pending_picks_ == nullptr); + grpc_connectivity_state_destroy(&state_tracker_); +} + +void PickFirst::HandOffPendingPicksLocked(LoadBalancingPolicy* new_policy) { + PickState* pick; + while ((pick = pending_picks_) != nullptr) { + pending_picks_ = pick->next; + grpc_error* error = GRPC_ERROR_NONE; + if (new_policy->PickLocked(pick, &error)) { + // Synchronous return, schedule closure. + GRPC_CLOSURE_SCHED(pick->on_complete, error); + } + } } void PickFirst::ShutdownLocked() { AutoChildRefsUpdater guard(this); + grpc_error* error = GRPC_ERROR_CREATE_FROM_STATIC_STRING("Channel shutdown"); if (grpc_lb_pick_first_trace.enabled()) { gpr_log(GPR_INFO, "Pick First %p Shutting down", this); } shutdown_ = true; + PickState* pick; + while ((pick = pending_picks_) != nullptr) { + pending_picks_ = pick->next; + pick->connected_subchannel.reset(); + GRPC_CLOSURE_SCHED(pick->on_complete, GRPC_ERROR_REF(error)); + } + grpc_connectivity_state_set(&state_tracker_, GRPC_CHANNEL_SHUTDOWN, + GRPC_ERROR_REF(error), "shutdown"); subchannel_list_.reset(); latest_pending_subchannel_list_.reset(); + TryReresolutionLocked(&grpc_lb_pick_first_trace, GRPC_ERROR_CANCELLED); + GRPC_ERROR_UNREF(error); +} + +void PickFirst::CancelPickLocked(PickState* pick, grpc_error* error) { + PickState* pp = pending_picks_; + pending_picks_ = nullptr; + while (pp != nullptr) { + PickState* next = pp->next; + if (pp == pick) { + pick->connected_subchannel.reset(); + GRPC_CLOSURE_SCHED(pick->on_complete, + GRPC_ERROR_CREATE_REFERENCING_FROM_STATIC_STRING( + "Pick Cancelled", &error, 1)); + } else { + pp->next = pending_picks_; + pending_picks_ = pp; + } + pp = next; + } + GRPC_ERROR_UNREF(error); +} + +void PickFirst::CancelMatchingPicksLocked(uint32_t initial_metadata_flags_mask, + uint32_t initial_metadata_flags_eq, + grpc_error* error) { + PickState* pick = pending_picks_; + pending_picks_ = nullptr; + while (pick != nullptr) { + PickState* next = pick->next; + if ((*pick->initial_metadata_flags & initial_metadata_flags_mask) == + initial_metadata_flags_eq) { + GRPC_CLOSURE_SCHED(pick->on_complete, + GRPC_ERROR_CREATE_REFERENCING_FROM_STATIC_STRING( + "Pick Cancelled", &error, 1)); + } else { + pick->next = pending_picks_; + pending_picks_ = pick; + } + pick = next; + } + GRPC_ERROR_UNREF(error); } void PickFirst::StartPickingLocked() { @@ -206,6 +270,36 @@ void PickFirst::ResetBackoffLocked() { } } +bool PickFirst::PickLocked(PickState* pick, grpc_error** error) { + // If we have a selected subchannel already, return synchronously. + if (selected_ != nullptr) { + pick->connected_subchannel = selected_->connected_subchannel()->Ref(); + return true; + } + // No subchannel selected yet, so handle asynchronously. + if (pick->on_complete == nullptr) { + *error = GRPC_ERROR_CREATE_FROM_STATIC_STRING( + "No pick result available but synchronous result required."); + return true; + } + pick->next = pending_picks_; + pending_picks_ = pick; + if (!started_picking_) { + StartPickingLocked(); + } + return false; +} + +grpc_connectivity_state PickFirst::CheckConnectivityLocked(grpc_error** error) { + return grpc_connectivity_state_get(&state_tracker_, error); +} + +void PickFirst::NotifyOnStateChangeLocked(grpc_connectivity_state* current, + grpc_closure* notify) { + grpc_connectivity_state_notify_on_state_change(&state_tracker_, current, + notify); +} + void PickFirst::FillChildRefsForChannelz( channelz::ChildRefsList* child_subchannels_to_fill, channelz::ChildRefsList* ignored) { @@ -247,11 +341,10 @@ void PickFirst::UpdateLocked(const grpc_channel_args& args, if (addresses == nullptr) { if (subchannel_list_ == nullptr) { // If we don't have a current subchannel list, go into TRANSIENT FAILURE. - grpc_error* error = - GRPC_ERROR_CREATE_FROM_STATIC_STRING("Missing update in args"); - channel_control_helper()->UpdateState( - GRPC_CHANNEL_TRANSIENT_FAILURE, GRPC_ERROR_REF(error), - UniquePtr(New(error))); + grpc_connectivity_state_set( + &state_tracker_, GRPC_CHANNEL_TRANSIENT_FAILURE, + GRPC_ERROR_CREATE_FROM_STATIC_STRING("Missing update in args"), + "pf_update_missing"); } else { // otherwise, keep using the current subchannel list (ignore this update). gpr_log(GPR_ERROR, @@ -271,17 +364,18 @@ void PickFirst::UpdateLocked(const grpc_channel_args& args, grpc_channel_args* new_args = grpc_channel_args_copy_and_add(&args, &new_arg, 1); auto subchannel_list = MakeOrphanable( - this, &grpc_lb_pick_first_trace, *addresses, combiner(), *new_args); + this, &grpc_lb_pick_first_trace, *addresses, combiner(), + client_channel_factory(), *new_args); grpc_channel_args_destroy(new_args); if (subchannel_list->num_subchannels() == 0) { // Empty update or no valid subchannels. Unsubscribe from all current // subchannels and put the channel in TRANSIENT_FAILURE. + grpc_connectivity_state_set( + &state_tracker_, GRPC_CHANNEL_TRANSIENT_FAILURE, + GRPC_ERROR_CREATE_FROM_STATIC_STRING("Empty update"), + "pf_update_empty"); subchannel_list_ = std::move(subchannel_list); // Empty list. selected_ = nullptr; - grpc_error* error = GRPC_ERROR_CREATE_FROM_STATIC_STRING("Empty update"); - channel_control_helper()->UpdateState( - GRPC_CHANNEL_TRANSIENT_FAILURE, GRPC_ERROR_REF(error), - UniquePtr(New(error))); return; } // If one of the subchannels in the new list is already in state @@ -359,8 +453,7 @@ void PickFirst::PickFirstSubchannelData::ProcessConnectivityChangeLocked( if (p->selected_ == this) { if (grpc_lb_pick_first_trace.enabled()) { gpr_log(GPR_INFO, - "Pick First %p selected subchannel connectivity changed to %s", p, - grpc_connectivity_state_name(connectivity_state)); + "Pick First %p connectivity changed for selected subchannel", p); } // If the new state is anything other than READY and there is a // pending update, switch to the pending update. @@ -376,12 +469,14 @@ void PickFirst::PickFirstSubchannelData::ProcessConnectivityChangeLocked( p->selected_ = nullptr; StopConnectivityWatchLocked(); p->subchannel_list_ = std::move(p->latest_pending_subchannel_list_); - grpc_error* new_error = GRPC_ERROR_CREATE_REFERENCING_FROM_STATIC_STRING( - "selected subchannel not ready; switching to pending update", &error, - 1); - p->channel_control_helper()->UpdateState( - GRPC_CHANNEL_TRANSIENT_FAILURE, GRPC_ERROR_REF(new_error), - UniquePtr(New(new_error))); + grpc_connectivity_state_set( + &p->state_tracker_, GRPC_CHANNEL_TRANSIENT_FAILURE, + error != GRPC_ERROR_NONE + ? GRPC_ERROR_REF(error) + : GRPC_ERROR_CREATE_FROM_STATIC_STRING( + "selected subchannel not ready; switching to pending " + "update"), + "selected_not_ready+switch_to_update"); } else { if (connectivity_state == GRPC_CHANNEL_TRANSIENT_FAILURE) { // If the selected subchannel goes bad, request a re-resolution. We also @@ -389,28 +484,17 @@ void PickFirst::PickFirstSubchannelData::ProcessConnectivityChangeLocked( // is that if the new state is TRANSIENT_FAILURE due to a GOAWAY // reception we don't want to connect to the re-resolved backends until // we leave the IDLE state. + grpc_connectivity_state_set(&p->state_tracker_, GRPC_CHANNEL_IDLE, + GRPC_ERROR_NONE, + "selected_changed+reresolve"); p->started_picking_ = false; - p->channel_control_helper()->RequestReresolution(); + p->TryReresolutionLocked(&grpc_lb_pick_first_trace, GRPC_ERROR_NONE); // In transient failure. Rely on re-resolution to recover. p->selected_ = nullptr; StopConnectivityWatchLocked(); - p->channel_control_helper()->UpdateState( - GRPC_CHANNEL_IDLE, GRPC_ERROR_NONE, - UniquePtr(New(p->Ref()))); } else { - // This is unlikely but can happen when a subchannel has been asked - // to reconnect by a different channel and this channel has dropped - // some connectivity state notifications. - if (connectivity_state == GRPC_CHANNEL_READY) { - p->channel_control_helper()->UpdateState( - GRPC_CHANNEL_READY, GRPC_ERROR_NONE, - UniquePtr( - New(connected_subchannel()->Ref()))); - } else { // CONNECTING - p->channel_control_helper()->UpdateState( - connectivity_state, GRPC_ERROR_REF(error), - UniquePtr(New(p->Ref()))); - } + grpc_connectivity_state_set(&p->state_tracker_, connectivity_state, + GRPC_ERROR_REF(error), "selected_changed"); // Renew notification. RenewConnectivityWatchLocked(); } @@ -443,14 +527,10 @@ void PickFirst::PickFirstSubchannelData::ProcessConnectivityChangeLocked( // Case 1: Only set state to TRANSIENT_FAILURE if we've tried // all subchannels. if (sd->Index() == 0 && subchannel_list() == p->subchannel_list_.get()) { - p->channel_control_helper()->RequestReresolution(); - grpc_error* new_error = - GRPC_ERROR_CREATE_REFERENCING_FROM_STATIC_STRING( - "failed to connect to all addresses", &error, 1); - p->channel_control_helper()->UpdateState( - GRPC_CHANNEL_TRANSIENT_FAILURE, GRPC_ERROR_REF(new_error), - UniquePtr( - New(new_error))); + p->TryReresolutionLocked(&grpc_lb_pick_first_trace, GRPC_ERROR_NONE); + grpc_connectivity_state_set( + &p->state_tracker_, GRPC_CHANNEL_TRANSIENT_FAILURE, + GRPC_ERROR_REF(error), "exhausted_subchannels"); } sd->CheckConnectivityStateAndStartWatchingLocked(); break; @@ -459,9 +539,9 @@ void PickFirst::PickFirstSubchannelData::ProcessConnectivityChangeLocked( case GRPC_CHANNEL_IDLE: { // Only update connectivity state in case 1. if (subchannel_list() == p->subchannel_list_.get()) { - p->channel_control_helper()->UpdateState( - GRPC_CHANNEL_CONNECTING, GRPC_ERROR_NONE, - UniquePtr(New(p->Ref()))); + grpc_connectivity_state_set(&p->state_tracker_, GRPC_CHANNEL_CONNECTING, + GRPC_ERROR_REF(error), + "connecting_changed"); } // Renew notification. RenewConnectivityWatchLocked(); @@ -498,13 +578,23 @@ void PickFirst::PickFirstSubchannelData::ProcessUnselectedReadyLocked() { p->subchannel_list_ = std::move(p->latest_pending_subchannel_list_); } // Cases 1 and 2. + grpc_connectivity_state_set(&p->state_tracker_, GRPC_CHANNEL_READY, + GRPC_ERROR_NONE, "subchannel_ready"); p->selected_ = this; - p->channel_control_helper()->UpdateState( - GRPC_CHANNEL_READY, GRPC_ERROR_NONE, - UniquePtr(New(connected_subchannel()->Ref()))); if (grpc_lb_pick_first_trace.enabled()) { gpr_log(GPR_INFO, "Pick First %p selected subchannel %p", p, subchannel()); } + // Update any calls that were waiting for a pick. + PickState* pick; + while ((pick = p->pending_picks_)) { + p->pending_picks_ = pick->next; + pick->connected_subchannel = p->selected_->connected_subchannel()->Ref(); + if (grpc_lb_pick_first_trace.enabled()) { + gpr_log(GPR_INFO, "Servicing pending pick with selected subchannel %p", + p->selected_->subchannel()); + } + GRPC_CLOSURE_SCHED(pick->on_complete, GRPC_ERROR_NONE); + } } void PickFirst::PickFirstSubchannelData:: diff --git a/src/core/ext/filters/client_channel/lb_policy/round_robin/round_robin.cc b/src/core/ext/filters/client_channel/lb_policy/round_robin/round_robin.cc index 0406efb71d3..aab6dd68216 100644 --- a/src/core/ext/filters/client_channel/lb_policy/round_robin/round_robin.cc +++ b/src/core/ext/filters/client_channel/lb_policy/round_robin/round_robin.cc @@ -26,7 +26,6 @@ #include -#include #include #include @@ -63,6 +62,16 @@ class RoundRobin : public LoadBalancingPolicy { void UpdateLocked(const grpc_channel_args& args, grpc_json* lb_config) override; + bool PickLocked(PickState* pick, grpc_error** error) override; + void CancelPickLocked(PickState* pick, grpc_error* error) override; + void CancelMatchingPicksLocked(uint32_t initial_metadata_flags_mask, + uint32_t initial_metadata_flags_eq, + grpc_error* error) override; + void NotifyOnStateChangeLocked(grpc_connectivity_state* state, + grpc_closure* closure) override; + grpc_connectivity_state CheckConnectivityLocked( + grpc_error** connectivity_error) override; + void HandOffPendingPicksLocked(LoadBalancingPolicy* new_policy) override; void ExitIdleLocked() override; void ResetBackoffLocked() override; void FillChildRefsForChannelz(channelz::ChildRefsList* child_subchannels, @@ -108,12 +117,14 @@ class RoundRobin : public LoadBalancingPolicy { : public SubchannelList { public: - RoundRobinSubchannelList(RoundRobin* policy, TraceFlag* tracer, - const ServerAddressList& addresses, - grpc_combiner* combiner, - const grpc_channel_args& args) + RoundRobinSubchannelList( + RoundRobin* policy, TraceFlag* tracer, + const ServerAddressList& addresses, grpc_combiner* combiner, + grpc_client_channel_factory* client_channel_factory, + const grpc_channel_args& args) : SubchannelList(policy, tracer, addresses, combiner, - policy->channel_control_helper(), args) { + client_channel_factory, args), + last_ready_index_(num_subchannels() - 1) { // Need to maintain a ref to the LB policy as long as we maintain // any references to subchannels, since the subchannels' // pollset_sets will include the LB policy's pollset_set. @@ -146,25 +157,15 @@ class RoundRobin : public LoadBalancingPolicy { // subchannels in each state. void UpdateRoundRobinStateFromSubchannelStateCountsLocked(); + size_t GetNextReadySubchannelIndexLocked(); + void UpdateLastReadySubchannelIndexLocked(size_t last_ready_index); + private: size_t num_ready_ = 0; size_t num_connecting_ = 0; size_t num_transient_failure_ = 0; grpc_error* last_transient_failure_error_ = GRPC_ERROR_NONE; - }; - - class Picker : public SubchannelPicker { - public: - Picker(RoundRobin* parent, RoundRobinSubchannelList* subchannel_list); - - PickResult Pick(PickState* pick, grpc_error** error) override; - - private: - // Using pointer value only, no ref held -- do not dereference! - RoundRobin* parent_; - - size_t last_picked_index_; - InlinedVector, 10> subchannels_; + size_t last_ready_index_; // Index into list of last pick. }; // Helper class to ensure that any function that modifies the child refs @@ -182,6 +183,8 @@ class RoundRobin : public LoadBalancingPolicy { void ShutdownLocked() override; void StartPickingLocked(); + bool DoPickLocked(PickState* pick); + void DrainPendingPicksLocked(); void UpdateChildRefsLocked(); /** list of subchannels */ @@ -196,6 +199,10 @@ class RoundRobin : public LoadBalancingPolicy { bool started_picking_ = false; /** are we shutting down? */ bool shutdown_ = false; + /** List of picks that are waiting on connectivity */ + PickState* pending_picks_ = nullptr; + /** our connectivity state tracker */ + grpc_connectivity_state_tracker state_tracker_; /// Lock and data used to capture snapshots of this channel's child /// channels and subchannels. This data is consumed by channelz. gpr_mu child_refs_mu_; @@ -203,62 +210,16 @@ class RoundRobin : public LoadBalancingPolicy { channelz::ChildRefsList child_channels_; }; -// -// RoundRobin::Picker -// - -RoundRobin::Picker::Picker(RoundRobin* parent, - RoundRobinSubchannelList* subchannel_list) - : parent_(parent) { - for (size_t i = 0; i < subchannel_list->num_subchannels(); ++i) { - auto* connected_subchannel = - subchannel_list->subchannel(i)->connected_subchannel(); - if (connected_subchannel != nullptr) { - subchannels_.push_back(connected_subchannel->Ref()); - } - } - // For discussion on why we generate a random starting index for - // the picker, see https://github.com/grpc/grpc-go/issues/2580. - // TODO(roth): rand(3) is not thread-safe. This should be replaced with - // something better as part of https://github.com/grpc/grpc/issues/17891. - last_picked_index_ = rand() % subchannels_.size(); - if (grpc_lb_round_robin_trace.enabled()) { - gpr_log(GPR_INFO, - "[RR %p picker %p] created picker from subchannel_list=%p " - "with %" PRIuPTR " READY subchannels; last_picked_index_=%" PRIuPTR, - parent_, this, subchannel_list, subchannels_.size(), - last_picked_index_); - } -} - -RoundRobin::Picker::PickResult RoundRobin::Picker::Pick(PickState* pick, - grpc_error** error) { - last_picked_index_ = (last_picked_index_ + 1) % subchannels_.size(); - if (grpc_lb_round_robin_trace.enabled()) { - gpr_log(GPR_INFO, - "[RR %p picker %p] returning index %" PRIuPTR - ", connected_subchannel=%p", - parent_, this, last_picked_index_, - subchannels_[last_picked_index_].get()); - } - pick->connected_subchannel = subchannels_[last_picked_index_]; - return PICK_COMPLETE; -} - -// -// RoundRobin -// - RoundRobin::RoundRobin(Args args) : LoadBalancingPolicy(std::move(args)) { + GPR_ASSERT(args.client_channel_factory != nullptr); gpr_mu_init(&child_refs_mu_); + grpc_connectivity_state_init(&state_tracker_, GRPC_CHANNEL_IDLE, + "round_robin"); + UpdateLocked(*args.args, args.lb_config); if (grpc_lb_round_robin_trace.enabled()) { - gpr_log(GPR_INFO, "[RR %p] Created", this); + gpr_log(GPR_INFO, "[RR %p] Created with %" PRIuPTR " subchannels", this, + subchannel_list_->num_subchannels()); } - // Initialize channel with a picker that will start us connecting. - channel_control_helper()->UpdateState( - GRPC_CHANNEL_IDLE, GRPC_ERROR_NONE, - UniquePtr(New(Ref()))); - UpdateLocked(*args.args, args.lb_config); } RoundRobin::~RoundRobin() { @@ -268,16 +229,82 @@ RoundRobin::~RoundRobin() { gpr_mu_destroy(&child_refs_mu_); GPR_ASSERT(subchannel_list_ == nullptr); GPR_ASSERT(latest_pending_subchannel_list_ == nullptr); + GPR_ASSERT(pending_picks_ == nullptr); + grpc_connectivity_state_destroy(&state_tracker_); +} + +void RoundRobin::HandOffPendingPicksLocked(LoadBalancingPolicy* new_policy) { + PickState* pick; + while ((pick = pending_picks_) != nullptr) { + pending_picks_ = pick->next; + grpc_error* error = GRPC_ERROR_NONE; + if (new_policy->PickLocked(pick, &error)) { + // Synchronous return, schedule closure. + GRPC_CLOSURE_SCHED(pick->on_complete, error); + } + } } void RoundRobin::ShutdownLocked() { AutoChildRefsUpdater guard(this); + grpc_error* error = GRPC_ERROR_CREATE_FROM_STATIC_STRING("Channel shutdown"); if (grpc_lb_round_robin_trace.enabled()) { gpr_log(GPR_INFO, "[RR %p] Shutting down", this); } shutdown_ = true; + PickState* pick; + while ((pick = pending_picks_) != nullptr) { + pending_picks_ = pick->next; + pick->connected_subchannel.reset(); + GRPC_CLOSURE_SCHED(pick->on_complete, GRPC_ERROR_REF(error)); + } + grpc_connectivity_state_set(&state_tracker_, GRPC_CHANNEL_SHUTDOWN, + GRPC_ERROR_REF(error), "rr_shutdown"); subchannel_list_.reset(); latest_pending_subchannel_list_.reset(); + TryReresolutionLocked(&grpc_lb_round_robin_trace, GRPC_ERROR_CANCELLED); + GRPC_ERROR_UNREF(error); +} + +void RoundRobin::CancelPickLocked(PickState* pick, grpc_error* error) { + PickState* pp = pending_picks_; + pending_picks_ = nullptr; + while (pp != nullptr) { + PickState* next = pp->next; + if (pp == pick) { + pick->connected_subchannel.reset(); + GRPC_CLOSURE_SCHED(pick->on_complete, + GRPC_ERROR_CREATE_REFERENCING_FROM_STATIC_STRING( + "Pick Cancelled", &error, 1)); + } else { + pp->next = pending_picks_; + pending_picks_ = pp; + } + pp = next; + } + GRPC_ERROR_UNREF(error); +} + +void RoundRobin::CancelMatchingPicksLocked(uint32_t initial_metadata_flags_mask, + uint32_t initial_metadata_flags_eq, + grpc_error* error) { + PickState* pick = pending_picks_; + pending_picks_ = nullptr; + while (pick != nullptr) { + PickState* next = pick->next; + if ((*pick->initial_metadata_flags & initial_metadata_flags_mask) == + initial_metadata_flags_eq) { + pick->connected_subchannel.reset(); + GRPC_CLOSURE_SCHED(pick->on_complete, + GRPC_ERROR_CREATE_REFERENCING_FROM_STATIC_STRING( + "Pick Cancelled", &error, 1)); + } else { + pick->next = pending_picks_; + pending_picks_ = pick; + } + pick = next; + } + GRPC_ERROR_UNREF(error); } void RoundRobin::StartPickingLocked() { @@ -298,6 +325,60 @@ void RoundRobin::ResetBackoffLocked() { } } +bool RoundRobin::DoPickLocked(PickState* pick) { + const size_t next_ready_index = + subchannel_list_->GetNextReadySubchannelIndexLocked(); + if (next_ready_index < subchannel_list_->num_subchannels()) { + /* readily available, report right away */ + RoundRobinSubchannelData* sd = + subchannel_list_->subchannel(next_ready_index); + GPR_ASSERT(sd->connected_subchannel() != nullptr); + pick->connected_subchannel = sd->connected_subchannel()->Ref(); + if (grpc_lb_round_robin_trace.enabled()) { + gpr_log(GPR_INFO, + "[RR %p] Picked target <-- Subchannel %p (connected %p) (sl %p, " + "index %" PRIuPTR ")", + this, sd->subchannel(), pick->connected_subchannel.get(), + sd->subchannel_list(), next_ready_index); + } + /* only advance the last picked pointer if the selection was used */ + subchannel_list_->UpdateLastReadySubchannelIndexLocked(next_ready_index); + return true; + } + return false; +} + +void RoundRobin::DrainPendingPicksLocked() { + PickState* pick; + while ((pick = pending_picks_)) { + pending_picks_ = pick->next; + GPR_ASSERT(DoPickLocked(pick)); + GRPC_CLOSURE_SCHED(pick->on_complete, GRPC_ERROR_NONE); + } +} + +bool RoundRobin::PickLocked(PickState* pick, grpc_error** error) { + if (grpc_lb_round_robin_trace.enabled()) { + gpr_log(GPR_INFO, "[RR %p] Trying to pick (shutdown: %d)", this, shutdown_); + } + GPR_ASSERT(!shutdown_); + if (subchannel_list_ != nullptr) { + if (DoPickLocked(pick)) return true; + } + if (pick->on_complete == nullptr) { + *error = GRPC_ERROR_CREATE_FROM_STATIC_STRING( + "No pick result available but synchronous result required."); + return true; + } + /* no pick currently available. Save for later in list of pending picks */ + pick->next = pending_picks_; + pending_picks_ = pick; + if (!started_picking_) { + StartPickingLocked(); + } + return false; +} + void RoundRobin::FillChildRefsForChannelz( channelz::ChildRefsList* child_subchannels_to_fill, channelz::ChildRefsList* ignored) { @@ -381,8 +462,8 @@ void RoundRobin::RoundRobinSubchannelList::UpdateStateCountersLocked( last_transient_failure_error_ = transient_failure_error; } -// Sets the RR policy's connectivity state and generates a new picker based -// on the current subchannel list. +// Sets the RR policy's connectivity state based on the current +// subchannel list. void RoundRobin::RoundRobinSubchannelList:: MaybeUpdateRoundRobinConnectivityStateLocked() { RoundRobin* p = static_cast(policy()); @@ -404,21 +485,18 @@ void RoundRobin::RoundRobinSubchannelList:: */ if (num_ready_ > 0) { /* 1) READY */ - p->channel_control_helper()->UpdateState( - GRPC_CHANNEL_READY, GRPC_ERROR_NONE, - UniquePtr(New(p, this))); + grpc_connectivity_state_set(&p->state_tracker_, GRPC_CHANNEL_READY, + GRPC_ERROR_NONE, "rr_ready"); } else if (num_connecting_ > 0) { /* 2) CONNECTING */ - p->channel_control_helper()->UpdateState( - GRPC_CHANNEL_CONNECTING, GRPC_ERROR_NONE, - UniquePtr(New(p->Ref()))); + grpc_connectivity_state_set(&p->state_tracker_, GRPC_CHANNEL_CONNECTING, + GRPC_ERROR_NONE, "rr_connecting"); } else if (num_transient_failure_ == num_subchannels()) { /* 3) TRANSIENT_FAILURE */ - p->channel_control_helper()->UpdateState( - GRPC_CHANNEL_TRANSIENT_FAILURE, - GRPC_ERROR_REF(last_transient_failure_error_), - UniquePtr(New( - GRPC_ERROR_REF(last_transient_failure_error_)))); + grpc_connectivity_state_set(&p->state_tracker_, + GRPC_CHANNEL_TRANSIENT_FAILURE, + GRPC_ERROR_REF(last_transient_failure_error_), + "rr_exhausted_subchannels"); } } @@ -447,6 +525,8 @@ void RoundRobin::RoundRobinSubchannelList:: } p->subchannel_list_ = std::move(p->latest_pending_subchannel_list_); } + // Drain pending picks. + p->DrainPendingPicksLocked(); } // Update the RR policy's connectivity state if needed. MaybeUpdateRoundRobinConnectivityStateLocked(); @@ -486,7 +566,7 @@ void RoundRobin::RoundRobinSubchannelData::ProcessConnectivityChangeLocked( "Requesting re-resolution", p, subchannel()); } - p->channel_control_helper()->RequestReresolution(); + p->TryReresolutionLocked(&grpc_lb_round_robin_trace, GRPC_ERROR_NONE); } // Update state counters. UpdateConnectivityStateLocked(connectivity_state, error); @@ -495,6 +575,73 @@ void RoundRobin::RoundRobinSubchannelData::ProcessConnectivityChangeLocked( RenewConnectivityWatchLocked(); } +/** Returns the index into p->subchannel_list->subchannels of the next + * subchannel in READY state, or p->subchannel_list->num_subchannels if no + * subchannel is READY. + * + * Note that this function does *not* update p->last_ready_subchannel_index. + * The caller must do that if it returns a pick. */ +size_t +RoundRobin::RoundRobinSubchannelList::GetNextReadySubchannelIndexLocked() { + if (grpc_lb_round_robin_trace.enabled()) { + gpr_log(GPR_INFO, + "[RR %p] getting next ready subchannel (out of %" PRIuPTR + "), last_ready_index=%" PRIuPTR, + policy(), num_subchannels(), last_ready_index_); + } + for (size_t i = 0; i < num_subchannels(); ++i) { + const size_t index = (i + last_ready_index_ + 1) % num_subchannels(); + if (grpc_lb_round_robin_trace.enabled()) { + gpr_log( + GPR_INFO, + "[RR %p] checking subchannel %p, subchannel_list %p, index %" PRIuPTR + ": state=%s", + policy(), subchannel(index)->subchannel(), this, index, + grpc_connectivity_state_name( + subchannel(index)->connectivity_state())); + } + if (subchannel(index)->connectivity_state() == GRPC_CHANNEL_READY) { + if (grpc_lb_round_robin_trace.enabled()) { + gpr_log(GPR_INFO, + "[RR %p] found next ready subchannel (%p) at index %" PRIuPTR + " of subchannel_list %p", + policy(), subchannel(index)->subchannel(), index, this); + } + return index; + } + } + if (grpc_lb_round_robin_trace.enabled()) { + gpr_log(GPR_INFO, "[RR %p] no subchannels in ready state", this); + } + return num_subchannels(); +} + +// Sets last_ready_index_ to last_ready_index. +void RoundRobin::RoundRobinSubchannelList::UpdateLastReadySubchannelIndexLocked( + size_t last_ready_index) { + GPR_ASSERT(last_ready_index < num_subchannels()); + last_ready_index_ = last_ready_index; + if (grpc_lb_round_robin_trace.enabled()) { + gpr_log(GPR_INFO, + "[RR %p] setting last_ready_subchannel_index=%" PRIuPTR + " (SC %p, CSC %p)", + policy(), last_ready_index, + subchannel(last_ready_index)->subchannel(), + subchannel(last_ready_index)->connected_subchannel()); + } +} + +grpc_connectivity_state RoundRobin::CheckConnectivityLocked( + grpc_error** error) { + return grpc_connectivity_state_get(&state_tracker_, error); +} + +void RoundRobin::NotifyOnStateChangeLocked(grpc_connectivity_state* current, + grpc_closure* notify) { + grpc_connectivity_state_notify_on_state_change(&state_tracker_, current, + notify); +} + void RoundRobin::UpdateLocked(const grpc_channel_args& args, grpc_json* lb_config) { AutoChildRefsUpdater guard(this); @@ -504,11 +651,10 @@ void RoundRobin::UpdateLocked(const grpc_channel_args& args, // If we don't have a current subchannel list, go into TRANSIENT_FAILURE. // Otherwise, keep using the current subchannel list (ignore this update). if (subchannel_list_ == nullptr) { - grpc_error* error = - GRPC_ERROR_CREATE_FROM_STATIC_STRING("Missing update in args"); - channel_control_helper()->UpdateState( - GRPC_CHANNEL_TRANSIENT_FAILURE, GRPC_ERROR_REF(error), - UniquePtr(New(error))); + grpc_connectivity_state_set( + &state_tracker_, GRPC_CHANNEL_TRANSIENT_FAILURE, + GRPC_ERROR_CREATE_FROM_STATIC_STRING("Missing update in args"), + "rr_update_missing"); } return; } @@ -525,16 +671,17 @@ void RoundRobin::UpdateLocked(const grpc_channel_args& args, } } latest_pending_subchannel_list_ = MakeOrphanable( - this, &grpc_lb_round_robin_trace, *addresses, combiner(), args); + this, &grpc_lb_round_robin_trace, *addresses, combiner(), + client_channel_factory(), args); // If we haven't started picking yet or the new list is empty, // immediately promote the new list to the current list. if (!started_picking_ || latest_pending_subchannel_list_->num_subchannels() == 0) { if (latest_pending_subchannel_list_->num_subchannels() == 0) { - grpc_error* error = GRPC_ERROR_CREATE_FROM_STATIC_STRING("Empty update"); - channel_control_helper()->UpdateState( - GRPC_CHANNEL_TRANSIENT_FAILURE, GRPC_ERROR_REF(error), - UniquePtr(New(error))); + grpc_connectivity_state_set( + &state_tracker_, GRPC_CHANNEL_TRANSIENT_FAILURE, + GRPC_ERROR_CREATE_FROM_STATIC_STRING("Empty update"), + "rr_update_empty"); } subchannel_list_ = std::move(latest_pending_subchannel_list_); } else { diff --git a/src/core/ext/filters/client_channel/lb_policy/subchannel_list.h b/src/core/ext/filters/client_channel/lb_policy/subchannel_list.h index c262dfe60f5..0174a98a73d 100644 --- a/src/core/ext/filters/client_channel/lb_policy/subchannel_list.h +++ b/src/core/ext/filters/client_channel/lb_policy/subchannel_list.h @@ -232,7 +232,7 @@ class SubchannelList : public InternallyRefCounted { protected: SubchannelList(LoadBalancingPolicy* policy, TraceFlag* tracer, const ServerAddressList& addresses, grpc_combiner* combiner, - LoadBalancingPolicy::ChannelControlHelper* helper, + grpc_client_channel_factory* client_channel_factory, const grpc_channel_args& args); virtual ~SubchannelList(); @@ -486,7 +486,7 @@ template SubchannelList::SubchannelList( LoadBalancingPolicy* policy, TraceFlag* tracer, const ServerAddressList& addresses, grpc_combiner* combiner, - LoadBalancingPolicy::ChannelControlHelper* helper, + grpc_client_channel_factory* client_channel_factory, const grpc_channel_args& args) : InternallyRefCounted(tracer), policy_(policy), @@ -509,8 +509,12 @@ SubchannelList::SubchannelList( GRPC_ARG_INHIBIT_HEALTH_CHECKING}; // Create a subchannel for each address. for (size_t i = 0; i < addresses.size(); i++) { + // If there were any balancer addresses, we would have chosen grpclb + // policy, which does not use a SubchannelList. GPR_ASSERT(!addresses[i].IsBalancer()); - InlinedVector args_to_add; + InlinedVector args_to_add; + args_to_add.emplace_back( + SubchannelPoolInterface::CreateChannelArg(policy_->subchannel_pool())); const size_t subchannel_address_arg_index = args_to_add.size(); args_to_add.emplace_back( Subchannel::CreateSubchannelAddressArg(&addresses[i].address())); @@ -523,7 +527,8 @@ SubchannelList::SubchannelList( &args, keys_to_remove, GPR_ARRAY_SIZE(keys_to_remove), args_to_add.data(), args_to_add.size()); gpr_free(args_to_add[subchannel_address_arg_index].value.string); - Subchannel* subchannel = helper->CreateSubchannel(*new_args); + Subchannel* subchannel = grpc_client_channel_factory_create_subchannel( + client_channel_factory, new_args); grpc_channel_args_destroy(new_args); if (subchannel == nullptr) { // Subchannel could not be created. diff --git a/src/core/ext/filters/client_channel/lb_policy/xds/xds.cc b/src/core/ext/filters/client_channel/lb_policy/xds/xds.cc index 4b3f2882424..678b4d75eb9 100644 --- a/src/core/ext/filters/client_channel/lb_policy/xds/xds.cc +++ b/src/core/ext/filters/client_channel/lb_policy/xds/xds.cc @@ -70,6 +70,7 @@ #include #include "src/core/ext/filters/client_channel/client_channel.h" +#include "src/core/ext/filters/client_channel/client_channel_factory.h" #include "src/core/ext/filters/client_channel/lb_policy/xds/xds.h" #include "src/core/ext/filters/client_channel/lb_policy/xds/xds_channel.h" #include "src/core/ext/filters/client_channel/lb_policy/xds/xds_client_stats.h" @@ -124,6 +125,16 @@ class XdsLb : public LoadBalancingPolicy { void UpdateLocked(const grpc_channel_args& args, grpc_json* lb_config) override; + bool PickLocked(PickState* pick, grpc_error** error) override; + void CancelPickLocked(PickState* pick, grpc_error* error) override; + void CancelMatchingPicksLocked(uint32_t initial_metadata_flags_mask, + uint32_t initial_metadata_flags_eq, + grpc_error* error) override; + void NotifyOnStateChangeLocked(grpc_connectivity_state* state, + grpc_closure* closure) override; + grpc_connectivity_state CheckConnectivityLocked( + grpc_error** connectivity_error) override; + void HandOffPendingPicksLocked(LoadBalancingPolicy* new_policy) override; void ExitIdleLocked() override; void ResetBackoffLocked() override; void FillChildRefsForChannelz( @@ -131,6 +142,31 @@ class XdsLb : public LoadBalancingPolicy { channelz::ChildRefsList* child_channels) override; private: + /// Linked list of pending pick requests. It stores all information needed to + /// eventually call pick() on them. They mainly stay pending waiting for the + /// child policy to be created. + /// + /// Note that when a pick is sent to the child policy, we inject our own + /// on_complete callback, so that we can intercept the result before + /// invoking the original on_complete callback. This allows us to set the + /// LB token metadata and add client_stats to the call context. + /// See \a pending_pick_complete() for details. + struct PendingPick { + // The xds lb instance that created the wrapping. This instance is not + // owned; reference counts are untouched. It's used only for logging + // purposes. + XdsLb* xdslb_policy; + // The original pick. + PickState* pick; + // Our on_complete closure and the original one. + grpc_closure on_complete; + grpc_closure* original_on_complete; + // Stats for client-side load reporting. + RefCountedPtr client_stats; + // Next pending pick. + PendingPick* next = nullptr; + }; + /// Contains a call to the LB server and all the data related to the call. class BalancerCallState : public InternallyRefCounted { public: @@ -205,36 +241,6 @@ class XdsLb : public LoadBalancingPolicy { grpc_closure client_load_report_closure_; }; - class Picker : public SubchannelPicker { - public: - Picker(UniquePtr child_picker, - RefCountedPtr client_stats) - : child_picker_(std::move(child_picker)), - client_stats_(std::move(client_stats)) {} - - PickResult Pick(PickState* pick, grpc_error** error) override; - - private: - UniquePtr child_picker_; - RefCountedPtr client_stats_; - }; - - class Helper : public ChannelControlHelper { - public: - explicit Helper(RefCountedPtr parent) : parent_(std::move(parent)) {} - - Subchannel* CreateSubchannel(const grpc_channel_args& args) override; - grpc_channel* CreateChannel(const char* target, - grpc_client_channel_type type, - const grpc_channel_args& args) override; - void UpdateState(grpc_connectivity_state state, grpc_error* state_error, - UniquePtr picker) override; - void RequestReresolution() override; - - private: - RefCountedPtr parent_; - }; - ~XdsLb(); void ShutdownLocked() override; @@ -257,10 +263,24 @@ class XdsLb : public LoadBalancingPolicy { static void OnBalancerChannelConnectivityChangedLocked(void* arg, grpc_error* error); + // Pending pick methods. + static void PendingPickCleanup(PendingPick* pp); + PendingPick* PendingPickCreate(PickState* pick); + void AddPendingPick(PendingPick* pp); + static void OnPendingPickComplete(void* arg, grpc_error* error); + // Methods for dealing with the child policy. void CreateOrUpdateChildPolicyLocked(); grpc_channel_args* CreateChildPolicyArgsLocked(); void CreateChildPolicyLocked(const char* name, Args args); + bool PickFromChildPolicyLocked(bool force_async, PendingPick* pp, + grpc_error** error); + void UpdateConnectivityStateFromChildPolicyLocked( + grpc_error* child_state_error); + static void OnChildPolicyConnectivityChangedLocked(void* arg, + grpc_error* error); + static void OnChildPolicyRequestReresolutionLocked(void* arg, + grpc_error* error); // Who the client is trying to communicate with. const char* server_name_ = nullptr; @@ -274,6 +294,7 @@ class XdsLb : public LoadBalancingPolicy { // Internal state. bool started_picking_ = false; bool shutting_down_ = false; + grpc_connectivity_state_tracker state_tracker_; // The channel for communicating with the LB server. grpc_channel* lb_channel_ = nullptr; @@ -316,91 +337,17 @@ class XdsLb : public LoadBalancingPolicy { grpc_timer lb_fallback_timer_; grpc_closure lb_on_fallback_; + // Pending picks that are waiting on the xDS policy's connectivity. + PendingPick* pending_picks_ = nullptr; + // The policy to use for the backends. OrphanablePtr child_policy_; UniquePtr child_policy_json_string_; + grpc_connectivity_state child_connectivity_state_; + grpc_closure on_child_connectivity_changed_; + grpc_closure on_child_request_reresolution_; }; -// -// XdsLb::Picker -// - -// Destroy function used when embedding client stats in call context. -void DestroyClientStats(void* arg) { - static_cast(arg)->Unref(); -} - -XdsLb::Picker::PickResult XdsLb::Picker::Pick(PickState* pick, - grpc_error** error) { - // TODO(roth): Add support for drop handling. - // Forward pick to child policy. - PickResult result = child_picker_->Pick(pick, error); - // If pick succeeded, add client stats. - if (result == PickResult::PICK_COMPLETE && - pick->connected_subchannel != nullptr && client_stats_ != nullptr) { - pick->subchannel_call_context[GRPC_GRPCLB_CLIENT_STATS].value = - client_stats_->Ref().release(); - pick->subchannel_call_context[GRPC_GRPCLB_CLIENT_STATS].destroy = - DestroyClientStats; - } - return result; -} - -// -// XdsLb::Helper -// - -Subchannel* XdsLb::Helper::CreateSubchannel(const grpc_channel_args& args) { - if (parent_->shutting_down_) return nullptr; - return parent_->channel_control_helper()->CreateSubchannel(args); -} - -grpc_channel* XdsLb::Helper::CreateChannel(const char* target, - grpc_client_channel_type type, - const grpc_channel_args& args) { - if (parent_->shutting_down_) return nullptr; - return parent_->channel_control_helper()->CreateChannel(target, type, args); -} - -void XdsLb::Helper::UpdateState(grpc_connectivity_state state, - grpc_error* state_error, - UniquePtr picker) { - if (parent_->shutting_down_) { - GRPC_ERROR_UNREF(state_error); - return; - } - // TODO(juanlishen): When in fallback mode, pass the child picker - // through without wrapping it. (Or maybe use a different helper for - // the fallback policy?) - RefCountedPtr client_stats; - if (parent_->lb_calld_ != nullptr && - parent_->lb_calld_->client_stats() != nullptr) { - client_stats = parent_->lb_calld_->client_stats()->Ref(); - } - parent_->channel_control_helper()->UpdateState( - state, state_error, - UniquePtr( - New(std::move(picker), std::move(client_stats)))); -} - -void XdsLb::Helper::RequestReresolution() { - if (parent_->shutting_down_) return; - if (grpc_lb_xds_trace.enabled()) { - gpr_log(GPR_INFO, - "[xdslb %p] Re-resolution requested from the internal RR policy " - "(%p).", - parent_.get(), parent_->child_policy_.get()); - } - // If we are talking to a balancer, we expect to get updated addresses - // from the balancer, so we can ignore the re-resolution request from - // the RR policy. Otherwise, pass the re-resolution request up to the - // channel. - if (parent_->lb_calld_ == nullptr || - !parent_->lb_calld_->seen_initial_response()) { - parent_->channel_control_helper()->RequestReresolution(); - } -} - // // serverlist parsing code // @@ -762,7 +709,7 @@ void XdsLb::BalancerCallState::OnBalancerMessageReceivedLocked( // serverlist returned from the current LB call. if (lb_calld->client_stats_report_interval_ > 0 && lb_calld->client_stats_ == nullptr) { - lb_calld->client_stats_ = MakeRefCounted(); + lb_calld->client_stats_.reset(New()); // TODO(roth): We currently track this ref manually. Once the // ClosureRef API is ready, we should pass the RefCountedPtr<> along // with the callback. @@ -845,13 +792,13 @@ void XdsLb::BalancerCallState::OnBalancerStatusReceivedLocked( lb_calld->lb_call_, grpc_error_string(error)); gpr_free(status_details); } + xdslb_policy->TryReresolutionLocked(&grpc_lb_xds_trace, GRPC_ERROR_NONE); // If this lb_calld is still in use, this call ended because of a failure so // we want to retry connecting. Otherwise, we have deliberately ended this // call and no further action is required. if (lb_calld == xdslb_policy->lb_calld_.get()) { xdslb_policy->lb_calld_.reset(); GPR_ASSERT(!xdslb_policy->shutting_down_); - xdslb_policy->channel_control_helper()->RequestReresolution(); if (lb_calld->seen_initial_response_) { // If we lose connection to the LB server, reset the backoff and restart // the LB call immediately. @@ -972,6 +919,13 @@ XdsLb::XdsLb(LoadBalancingPolicy::Args args) GRPC_CLOSURE_INIT(&lb_channel_on_connectivity_changed_, &XdsLb::OnBalancerChannelConnectivityChangedLocked, this, grpc_combiner_scheduler(args.combiner)); + GRPC_CLOSURE_INIT(&on_child_connectivity_changed_, + &XdsLb::OnChildPolicyConnectivityChangedLocked, this, + grpc_combiner_scheduler(args.combiner)); + GRPC_CLOSURE_INIT(&on_child_request_reresolution_, + &XdsLb::OnChildPolicyRequestReresolutionLocked, this, + grpc_combiner_scheduler(args.combiner)); + grpc_connectivity_state_init(&state_tracker_, GRPC_CHANNEL_IDLE, "xds"); // Record server name. const grpc_arg* arg = grpc_channel_args_find(args.args, GRPC_ARG_SERVER_URI); const char* server_uri = grpc_channel_arg_get_string(arg); @@ -996,22 +950,21 @@ XdsLb::XdsLb(LoadBalancingPolicy::Args args) ParseLbConfig(args.lb_config); // Process channel args. ProcessChannelArgsLocked(*args.args); - // Initialize channel with a picker that will start us connecting. - channel_control_helper()->UpdateState( - GRPC_CHANNEL_IDLE, GRPC_ERROR_NONE, - UniquePtr(New(Ref()))); } XdsLb::~XdsLb() { + GPR_ASSERT(pending_picks_ == nullptr); gpr_mu_destroy(&lb_channel_mu_); gpr_free((void*)server_name_); grpc_channel_args_destroy(args_); + grpc_connectivity_state_destroy(&state_tracker_); if (serverlist_ != nullptr) { xds_grpclb_destroy_serverlist(serverlist_); } } void XdsLb::ShutdownLocked() { + grpc_error* error = GRPC_ERROR_CREATE_FROM_STATIC_STRING("Channel shutdown"); shutting_down_ = true; lb_calld_.reset(); if (retry_timer_callback_pending_) { @@ -1021,6 +974,7 @@ void XdsLb::ShutdownLocked() { grpc_timer_cancel(&lb_fallback_timer_); } child_policy_.reset(); + TryReresolutionLocked(&grpc_lb_xds_trace, GRPC_ERROR_CANCELLED); // We destroy the LB channel here instead of in our destructor because // destroying the channel triggers a last callback to // OnBalancerChannelConnectivityChangedLocked(), and we need to be @@ -1031,12 +985,109 @@ void XdsLb::ShutdownLocked() { lb_channel_ = nullptr; gpr_mu_unlock(&lb_channel_mu_); } + grpc_connectivity_state_set(&state_tracker_, GRPC_CHANNEL_SHUTDOWN, + GRPC_ERROR_REF(error), "xds_shutdown"); + // Clear pending picks. + PendingPick* pp; + while ((pp = pending_picks_) != nullptr) { + pending_picks_ = pp->next; + pp->pick->connected_subchannel.reset(); + // Note: pp is deleted in this callback. + GRPC_CLOSURE_SCHED(&pp->on_complete, GRPC_ERROR_REF(error)); + } + GRPC_ERROR_UNREF(error); } // // public methods // +void XdsLb::HandOffPendingPicksLocked(LoadBalancingPolicy* new_policy) { + PendingPick* pp; + while ((pp = pending_picks_) != nullptr) { + pending_picks_ = pp->next; + pp->pick->on_complete = pp->original_on_complete; + grpc_error* error = GRPC_ERROR_NONE; + if (new_policy->PickLocked(pp->pick, &error)) { + // Synchronous return; schedule closure. + GRPC_CLOSURE_SCHED(pp->pick->on_complete, error); + } + Delete(pp); + } +} + +// Cancel a specific pending pick. +// +// A pick progresses as follows: +// - If there's a child policy available, it'll be handed over to child policy +// (in CreateChildPolicyLocked()). From that point onwards, it'll be the +// child policy's responsibility. For cancellations, that implies the pick +// needs to be also cancelled by the child policy instance. +// - Otherwise, without a child policy instance, picks stay pending at this +// policy's level (xds), inside the pending_picks_ list. To cancel these, +// we invoke the completion closure and set the pick's connected +// subchannel to nullptr right here. +void XdsLb::CancelPickLocked(PickState* pick, grpc_error* error) { + PendingPick* pp = pending_picks_; + pending_picks_ = nullptr; + while (pp != nullptr) { + PendingPick* next = pp->next; + if (pp->pick == pick) { + pick->connected_subchannel.reset(); + // Note: pp is deleted in this callback. + GRPC_CLOSURE_SCHED(&pp->on_complete, + GRPC_ERROR_CREATE_REFERENCING_FROM_STATIC_STRING( + "Pick Cancelled", &error, 1)); + } else { + pp->next = pending_picks_; + pending_picks_ = pp; + } + pp = next; + } + if (child_policy_ != nullptr) { + child_policy_->CancelPickLocked(pick, GRPC_ERROR_REF(error)); + } + GRPC_ERROR_UNREF(error); +} + +// Cancel all pending picks. +// +// A pick progresses as follows: +// - If there's a child policy available, it'll be handed over to child policy +// (in CreateChildPolicyLocked()). From that point onwards, it'll be the +// child policy's responsibility. For cancellations, that implies the pick +// needs to be also cancelled by the child policy instance. +// - Otherwise, without a child policy instance, picks stay pending at this +// policy's level (xds), inside the pending_picks_ list. To cancel these, +// we invoke the completion closure and set the pick's connected +// subchannel to nullptr right here. +void XdsLb::CancelMatchingPicksLocked(uint32_t initial_metadata_flags_mask, + uint32_t initial_metadata_flags_eq, + grpc_error* error) { + PendingPick* pp = pending_picks_; + pending_picks_ = nullptr; + while (pp != nullptr) { + PendingPick* next = pp->next; + if ((*pp->pick->initial_metadata_flags & initial_metadata_flags_mask) == + initial_metadata_flags_eq) { + // Note: pp is deleted in this callback. + GRPC_CLOSURE_SCHED(&pp->on_complete, + GRPC_ERROR_CREATE_REFERENCING_FROM_STATIC_STRING( + "Pick Cancelled", &error, 1)); + } else { + pp->next = pending_picks_; + pending_picks_ = pp; + } + pp = next; + } + if (child_policy_ != nullptr) { + child_policy_->CancelMatchingPicksLocked(initial_metadata_flags_mask, + initial_metadata_flags_eq, + GRPC_ERROR_REF(error)); + } + GRPC_ERROR_UNREF(error); +} + void XdsLb::ExitIdleLocked() { if (!started_picking_) { StartPickingLocked(); @@ -1052,6 +1103,36 @@ void XdsLb::ResetBackoffLocked() { } } +bool XdsLb::PickLocked(PickState* pick, grpc_error** error) { + PendingPick* pp = PendingPickCreate(pick); + bool pick_done = false; + if (child_policy_ != nullptr) { + if (grpc_lb_xds_trace.enabled()) { + gpr_log(GPR_INFO, "[xdslb %p] about to PICK from policy %p", this, + child_policy_.get()); + } + pick_done = PickFromChildPolicyLocked(false /* force_async */, pp, error); + } else { // child_policy_ == NULL + if (pick->on_complete == nullptr) { + *error = GRPC_ERROR_CREATE_FROM_STATIC_STRING( + "No pick result available but synchronous result required."); + pick_done = true; + } else { + if (grpc_lb_xds_trace.enabled()) { + gpr_log(GPR_INFO, + "[xdslb %p] No child policy. Adding to xds's pending picks", + this); + } + AddPendingPick(pp); + if (!started_picking_) { + StartPickingLocked(); + } + pick_done = false; + } + } + return pick_done; +} + void XdsLb::FillChildRefsForChannelz(channelz::ChildRefsList* child_subchannels, channelz::ChildRefsList* child_channels) { // delegate to the child_policy_ to fill the children subchannels. @@ -1066,6 +1147,17 @@ void XdsLb::FillChildRefsForChannelz(channelz::ChildRefsList* child_subchannels, } } +grpc_connectivity_state XdsLb::CheckConnectivityLocked( + grpc_error** connectivity_error) { + return grpc_connectivity_state_get(&state_tracker_, connectivity_error); +} + +void XdsLb::NotifyOnStateChangeLocked(grpc_connectivity_state* current, + grpc_closure* closure) { + grpc_connectivity_state_notify_on_state_change(&state_tracker_, current, + closure); +} + void XdsLb::ProcessChannelArgsLocked(const grpc_channel_args& args) { const ServerAddressList* addresses = FindServerAddressListChannelArg(&args); if (addresses == nullptr) { @@ -1093,8 +1185,9 @@ void XdsLb::ProcessChannelArgsLocked(const grpc_channel_args& args) { char* uri_str; gpr_asprintf(&uri_str, "fake:///%s", server_name_); gpr_mu_lock(&lb_channel_mu_); - lb_channel_ = channel_control_helper()->CreateChannel( - uri_str, GRPC_CLIENT_CHANNEL_TYPE_LOAD_BALANCING, *lb_channel_args); + lb_channel_ = grpc_client_channel_factory_create_channel( + client_channel_factory(), uri_str, + GRPC_CLIENT_CHANNEL_TYPE_LOAD_BALANCING, lb_channel_args); gpr_mu_unlock(&lb_channel_mu_); GPR_ASSERT(lb_channel_ != nullptr); gpr_free(uri_str); @@ -1309,10 +1402,90 @@ void XdsLb::OnBalancerChannelConnectivityChangedLocked(void* arg, } } +// +// PendingPick +// + +// Destroy function used when embedding client stats in call context. +void DestroyClientStats(void* arg) { + static_cast(arg)->Unref(); +} + +void XdsLb::PendingPickCleanup(PendingPick* pp) { + // If connected_subchannel is nullptr, no pick has been made by the + // child policy (e.g., all addresses failed to connect). + if (pp->pick->connected_subchannel != nullptr) { + // Pass on client stats via context. Passes ownership of the reference. + if (pp->client_stats != nullptr) { + pp->pick->subchannel_call_context[GRPC_GRPCLB_CLIENT_STATS].value = + pp->client_stats.release(); + pp->pick->subchannel_call_context[GRPC_GRPCLB_CLIENT_STATS].destroy = + DestroyClientStats; + } + } else { + pp->client_stats.reset(); + } +} + +/* The \a on_complete closure passed as part of the pick requires keeping a + * reference to its associated child policy instance. We wrap this closure in + * order to unref the child policy instance upon its invocation */ +void XdsLb::OnPendingPickComplete(void* arg, grpc_error* error) { + PendingPick* pp = static_cast(arg); + PendingPickCleanup(pp); + GRPC_CLOSURE_SCHED(pp->original_on_complete, GRPC_ERROR_REF(error)); + Delete(pp); +} + +XdsLb::PendingPick* XdsLb::PendingPickCreate(PickState* pick) { + PendingPick* pp = New(); + pp->xdslb_policy = this; + pp->pick = pick; + GRPC_CLOSURE_INIT(&pp->on_complete, &XdsLb::OnPendingPickComplete, pp, + grpc_schedule_on_exec_ctx); + pp->original_on_complete = pick->on_complete; + pick->on_complete = &pp->on_complete; + return pp; +} + +void XdsLb::AddPendingPick(PendingPick* pp) { + pp->next = pending_picks_; + pending_picks_ = pp; +} + // // code for interacting with the child policy // +// Performs a pick over \a child_policy_. Given that a pick can return +// immediately (ignoring its completion callback), we need to perform the +// cleanups this callback would otherwise be responsible for. +// If \a force_async is true, then we will manually schedule the +// completion callback even if the pick is available immediately. +bool XdsLb::PickFromChildPolicyLocked(bool force_async, PendingPick* pp, + grpc_error** error) { + // Set client_stats. + if (lb_calld_ != nullptr && lb_calld_->client_stats() != nullptr) { + pp->client_stats = lb_calld_->client_stats()->Ref(); + } + // Pick via the child policy. + bool pick_done = child_policy_->PickLocked(pp->pick, error); + if (pick_done) { + PendingPickCleanup(pp); + if (force_async) { + GRPC_CLOSURE_SCHED(pp->original_on_complete, *error); + *error = GRPC_ERROR_NONE; + pick_done = false; + } + Delete(pp); + } + // else, the pending pick will be registered and taken care of by the + // pending pick list inside the child policy. Eventually, + // OnPendingPickComplete() will be called, which will (among other + // things) add the LB token to the call's initial metadata. + return pick_done; +} + void XdsLb::CreateChildPolicyLocked(const char* name, Args args) { GPR_ASSERT(child_policy_ == nullptr); child_policy_ = LoadBalancingPolicyRegistry::CreateLoadBalancingPolicy( @@ -1321,12 +1494,42 @@ void XdsLb::CreateChildPolicyLocked(const char* name, Args args) { gpr_log(GPR_ERROR, "[xdslb %p] Failure creating a child policy", this); return; } + // TODO(roth): We currently track this ref manually. Once the new + // ClosureRef API is done, pass the RefCountedPtr<> along with the closure. + auto self = Ref(DEBUG_LOCATION, "on_child_reresolution_requested"); + self.release(); + child_policy_->SetReresolutionClosureLocked(&on_child_request_reresolution_); + grpc_error* child_state_error = nullptr; + child_connectivity_state_ = + child_policy_->CheckConnectivityLocked(&child_state_error); + // Connectivity state is a function of the child policy updated/created. + UpdateConnectivityStateFromChildPolicyLocked(child_state_error); // Add the xDS's interested_parties pollset_set to that of the newly created // child policy. This will make the child policy progress upon activity on // xDS LB, which in turn is tied to the application's call. grpc_pollset_set_add_pollset_set(child_policy_->interested_parties(), interested_parties()); + // Subscribe to changes to the connectivity of the new child policy. + // TODO(roth): We currently track this ref manually. Once the new + // ClosureRef API is done, pass the RefCountedPtr<> along with the closure. + self = Ref(DEBUG_LOCATION, "on_child_connectivity_changed"); + self.release(); + child_policy_->NotifyOnStateChangeLocked(&child_connectivity_state_, + &on_child_connectivity_changed_); child_policy_->ExitIdleLocked(); + // Send pending picks to child policy. + PendingPick* pp; + while ((pp = pending_picks_)) { + pending_picks_ = pp->next; + if (grpc_lb_xds_trace.enabled()) { + gpr_log( + GPR_INFO, + "[xdslb %p] Pending pick about to (async) PICK from child policy %p", + this, child_policy_.get()); + } + grpc_error* error = GRPC_ERROR_NONE; + PickFromChildPolicyLocked(true /* force_async */, pp, &error); + } } grpc_channel_args* XdsLb::CreateChildPolicyArgsLocked() { @@ -1384,9 +1587,9 @@ void XdsLb::CreateOrUpdateChildPolicyLocked() { } else { LoadBalancingPolicy::Args lb_policy_args; lb_policy_args.combiner = combiner(); + lb_policy_args.client_channel_factory = client_channel_factory(); + lb_policy_args.subchannel_pool = subchannel_pool()->Ref(); lb_policy_args.args = args; - lb_policy_args.channel_control_helper = - UniquePtr(New(Ref())); lb_policy_args.lb_config = child_policy_config; CreateChildPolicyLocked(child_policy_name, std::move(lb_policy_args)); if (grpc_lb_xds_trace.enabled()) { @@ -1398,6 +1601,102 @@ void XdsLb::CreateOrUpdateChildPolicyLocked() { grpc_json_destroy(child_policy_json); } +void XdsLb::OnChildPolicyRequestReresolutionLocked(void* arg, + grpc_error* error) { + XdsLb* xdslb_policy = static_cast(arg); + if (xdslb_policy->shutting_down_ || error != GRPC_ERROR_NONE) { + xdslb_policy->Unref(DEBUG_LOCATION, "on_child_reresolution_requested"); + return; + } + if (grpc_lb_xds_trace.enabled()) { + gpr_log(GPR_INFO, + "[xdslb %p] Re-resolution requested from child policy " + "(%p).", + xdslb_policy, xdslb_policy->child_policy_.get()); + } + // If we are talking to a balancer, we expect to get updated addresses form + // the balancer, so we can ignore the re-resolution request from the child + // policy. + // Otherwise, handle the re-resolution request using the xds policy's + // original re-resolution closure. + if (xdslb_policy->lb_calld_ == nullptr || + !xdslb_policy->lb_calld_->seen_initial_response()) { + xdslb_policy->TryReresolutionLocked(&grpc_lb_xds_trace, GRPC_ERROR_NONE); + } + // Give back the wrapper closure to the child policy. + xdslb_policy->child_policy_->SetReresolutionClosureLocked( + &xdslb_policy->on_child_request_reresolution_); +} + +void XdsLb::UpdateConnectivityStateFromChildPolicyLocked( + grpc_error* child_state_error) { + const grpc_connectivity_state curr_glb_state = + grpc_connectivity_state_check(&state_tracker_); + /* The new connectivity status is a function of the previous one and the new + * input coming from the status of the child policy. + * + * current state (xds's) + * | + * v || I | C | R | TF | SD | <- new state (child policy's) + * ===++====+=====+=====+======+======+ + * I || I | C | R | [I] | [I] | + * ---++----+-----+-----+------+------+ + * C || I | C | R | [C] | [C] | + * ---++----+-----+-----+------+------+ + * R || I | C | R | [R] | [R] | + * ---++----+-----+-----+------+------+ + * TF || I | C | R | [TF] | [TF] | + * ---++----+-----+-----+------+------+ + * SD || NA | NA | NA | NA | NA | (*) + * ---++----+-----+-----+------+------+ + * + * A [STATE] indicates that the old child policy is kept. In those cases, + * STATE is the current state of xds, which is left untouched. + * + * In summary, if the new state is TRANSIENT_FAILURE or SHUTDOWN, stick to + * the previous child policy instance. + * + * Note that the status is never updated to SHUTDOWN as a result of calling + * this function. Only glb_shutdown() has the power to set that state. + * + * (*) This function mustn't be called during shutting down. */ + GPR_ASSERT(curr_glb_state != GRPC_CHANNEL_SHUTDOWN); + switch (child_connectivity_state_) { + case GRPC_CHANNEL_TRANSIENT_FAILURE: + case GRPC_CHANNEL_SHUTDOWN: + GPR_ASSERT(child_state_error != GRPC_ERROR_NONE); + break; + case GRPC_CHANNEL_IDLE: + case GRPC_CHANNEL_CONNECTING: + case GRPC_CHANNEL_READY: + GPR_ASSERT(child_state_error == GRPC_ERROR_NONE); + } + if (grpc_lb_xds_trace.enabled()) { + gpr_log(GPR_INFO, + "[xdslb %p] Setting xds's state to %s from child policy %p state.", + this, grpc_connectivity_state_name(child_connectivity_state_), + child_policy_.get()); + } + grpc_connectivity_state_set(&state_tracker_, child_connectivity_state_, + child_state_error, + "update_lb_connectivity_status_locked"); +} + +void XdsLb::OnChildPolicyConnectivityChangedLocked(void* arg, + grpc_error* error) { + XdsLb* xdslb_policy = static_cast(arg); + if (xdslb_policy->shutting_down_) { + xdslb_policy->Unref(DEBUG_LOCATION, "on_child_connectivity_changed"); + return; + } + xdslb_policy->UpdateConnectivityStateFromChildPolicyLocked( + GRPC_ERROR_REF(error)); + // Resubscribe. Reuse the "on_child_connectivity_changed" ref. + xdslb_policy->child_policy_->NotifyOnStateChangeLocked( + &xdslb_policy->child_connectivity_state_, + &xdslb_policy->on_child_connectivity_changed_); +} + // // factory // diff --git a/src/core/ext/filters/client_channel/request_routing.cc b/src/core/ext/filters/client_channel/request_routing.cc new file mode 100644 index 00000000000..d6ff34c99b5 --- /dev/null +++ b/src/core/ext/filters/client_channel/request_routing.cc @@ -0,0 +1,946 @@ +/* + * + * Copyright 2015 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. + * + */ + +#include + +#include "src/core/ext/filters/client_channel/request_routing.h" + +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "src/core/ext/filters/client_channel/backup_poller.h" +#include "src/core/ext/filters/client_channel/global_subchannel_pool.h" +#include "src/core/ext/filters/client_channel/http_connect_handshaker.h" +#include "src/core/ext/filters/client_channel/lb_policy_registry.h" +#include "src/core/ext/filters/client_channel/local_subchannel_pool.h" +#include "src/core/ext/filters/client_channel/proxy_mapper_registry.h" +#include "src/core/ext/filters/client_channel/resolver_registry.h" +#include "src/core/ext/filters/client_channel/retry_throttle.h" +#include "src/core/ext/filters/client_channel/server_address.h" +#include "src/core/ext/filters/client_channel/subchannel.h" +#include "src/core/ext/filters/deadline/deadline_filter.h" +#include "src/core/lib/backoff/backoff.h" +#include "src/core/lib/channel/channel_args.h" +#include "src/core/lib/channel/connected_channel.h" +#include "src/core/lib/channel/status_util.h" +#include "src/core/lib/gpr/string.h" +#include "src/core/lib/gprpp/inlined_vector.h" +#include "src/core/lib/gprpp/manual_constructor.h" +#include "src/core/lib/iomgr/combiner.h" +#include "src/core/lib/iomgr/iomgr.h" +#include "src/core/lib/iomgr/polling_entity.h" +#include "src/core/lib/profiling/timers.h" +#include "src/core/lib/slice/slice_internal.h" +#include "src/core/lib/slice/slice_string_helpers.h" +#include "src/core/lib/surface/channel.h" +#include "src/core/lib/transport/connectivity_state.h" +#include "src/core/lib/transport/error_utils.h" +#include "src/core/lib/transport/metadata.h" +#include "src/core/lib/transport/metadata_batch.h" +#include "src/core/lib/transport/service_config.h" +#include "src/core/lib/transport/static_metadata.h" +#include "src/core/lib/transport/status_metadata.h" + +namespace grpc_core { + +// +// RequestRouter::Request::ResolverResultWaiter +// + +// Handles waiting for a resolver result. +// Used only for the first call on an idle channel. +class RequestRouter::Request::ResolverResultWaiter { + public: + explicit ResolverResultWaiter(Request* request) + : request_router_(request->request_router_), + request_(request), + tracer_enabled_(request_router_->tracer_->enabled()) { + if (tracer_enabled_) { + gpr_log(GPR_INFO, + "request_router=%p request=%p: deferring pick pending resolver " + "result", + request_router_, request); + } + // Add closure to be run when a resolver result is available. + GRPC_CLOSURE_INIT(&done_closure_, &DoneLocked, this, + grpc_combiner_scheduler(request_router_->combiner_)); + AddToWaitingList(); + // Set cancellation closure, so that we abort if the call is cancelled. + GRPC_CLOSURE_INIT(&cancel_closure_, &CancelLocked, this, + grpc_combiner_scheduler(request_router_->combiner_)); + grpc_call_combiner_set_notify_on_cancel(request->call_combiner_, + &cancel_closure_); + } + + private: + // Adds done_closure_ to + // request_router_->waiting_for_resolver_result_closures_. + void AddToWaitingList() { + grpc_closure_list_append( + &request_router_->waiting_for_resolver_result_closures_, &done_closure_, + GRPC_ERROR_NONE); + } + + // Invoked when a resolver result is available. + static void DoneLocked(void* arg, grpc_error* error) { + ResolverResultWaiter* self = static_cast(arg); + RequestRouter* request_router = self->request_router_; + // If CancelLocked() has already run, delete ourselves without doing + // anything. Note that the call stack may have already been destroyed, + // so it's not safe to access anything in state_. + if (GPR_UNLIKELY(self->finished_)) { + if (self->tracer_enabled_) { + gpr_log(GPR_INFO, + "request_router=%p: call cancelled before resolver result", + request_router); + } + Delete(self); + return; + } + // Otherwise, process the resolver result. + Request* request = self->request_; + if (GPR_UNLIKELY(error != GRPC_ERROR_NONE)) { + if (self->tracer_enabled_) { + gpr_log(GPR_INFO, + "request_router=%p request=%p: resolver failed to return data", + request_router, request); + } + GRPC_CLOSURE_RUN(request->on_route_done_, GRPC_ERROR_REF(error)); + } else if (GPR_UNLIKELY(request_router->resolver_ == nullptr)) { + // Shutting down. + if (self->tracer_enabled_) { + gpr_log(GPR_INFO, "request_router=%p request=%p: resolver disconnected", + request_router, request); + } + GRPC_CLOSURE_RUN(request->on_route_done_, + GRPC_ERROR_CREATE_FROM_STATIC_STRING("Disconnected")); + } else if (GPR_UNLIKELY(request_router->lb_policy_ == nullptr)) { + // Transient resolver failure. + // If call has wait_for_ready=true, try again; otherwise, fail. + if (*request->pick_.initial_metadata_flags & + GRPC_INITIAL_METADATA_WAIT_FOR_READY) { + if (self->tracer_enabled_) { + gpr_log(GPR_INFO, + "request_router=%p request=%p: resolver returned but no LB " + "policy; wait_for_ready=true; trying again", + request_router, request); + } + // Re-add ourselves to the waiting list. + self->AddToWaitingList(); + // Return early so that we don't set finished_ to true below. + return; + } else { + if (self->tracer_enabled_) { + gpr_log(GPR_INFO, + "request_router=%p request=%p: resolver returned but no LB " + "policy; wait_for_ready=false; failing", + request_router, request); + } + GRPC_CLOSURE_RUN( + request->on_route_done_, + grpc_error_set_int( + GRPC_ERROR_CREATE_FROM_STATIC_STRING("Name resolution failure"), + GRPC_ERROR_INT_GRPC_STATUS, GRPC_STATUS_UNAVAILABLE)); + } + } else { + if (self->tracer_enabled_) { + gpr_log(GPR_INFO, + "request_router=%p request=%p: resolver returned, doing LB " + "pick", + request_router, request); + } + request->ProcessServiceConfigAndStartLbPickLocked(); + } + self->finished_ = true; + } + + // Invoked when the call is cancelled. + // Note: This runs under the client_channel combiner, but will NOT be + // holding the call combiner. + static void CancelLocked(void* arg, grpc_error* error) { + ResolverResultWaiter* self = static_cast(arg); + RequestRouter* request_router = self->request_router_; + // If DoneLocked() has already run, delete ourselves without doing anything. + if (self->finished_) { + Delete(self); + return; + } + Request* request = self->request_; + // If we are being cancelled, immediately invoke on_route_done_ + // to propagate the error back to the caller. + if (error != GRPC_ERROR_NONE) { + if (self->tracer_enabled_) { + gpr_log(GPR_INFO, + "request_router=%p request=%p: cancelling call waiting for " + "name resolution", + request_router, request); + } + // Note: Although we are not in the call combiner here, we are + // basically stealing the call combiner from the pending pick, so + // it's safe to run on_route_done_ here -- we are essentially + // calling it here instead of calling it in DoneLocked(). + GRPC_CLOSURE_RUN(request->on_route_done_, + GRPC_ERROR_CREATE_REFERENCING_FROM_STATIC_STRING( + "Pick cancelled", &error, 1)); + } + self->finished_ = true; + } + + RequestRouter* request_router_; + Request* request_; + const bool tracer_enabled_; + grpc_closure done_closure_; + grpc_closure cancel_closure_; + bool finished_ = false; +}; + +// +// RequestRouter::Request::AsyncPickCanceller +// + +// Handles the call combiner cancellation callback for an async LB pick. +class RequestRouter::Request::AsyncPickCanceller { + public: + explicit AsyncPickCanceller(Request* request) + : request_router_(request->request_router_), + request_(request), + tracer_enabled_(request_router_->tracer_->enabled()) { + GRPC_CALL_STACK_REF(request->owning_call_, "pick_callback_cancel"); + // Set cancellation closure, so that we abort if the call is cancelled. + GRPC_CLOSURE_INIT(&cancel_closure_, &CancelLocked, this, + grpc_combiner_scheduler(request_router_->combiner_)); + grpc_call_combiner_set_notify_on_cancel(request->call_combiner_, + &cancel_closure_); + } + + void MarkFinishedLocked() { + finished_ = true; + GRPC_CALL_STACK_UNREF(request_->owning_call_, "pick_callback_cancel"); + } + + private: + // Invoked when the call is cancelled. + // Note: This runs under the client_channel combiner, but will NOT be + // holding the call combiner. + static void CancelLocked(void* arg, grpc_error* error) { + AsyncPickCanceller* self = static_cast(arg); + Request* request = self->request_; + RequestRouter* request_router = self->request_router_; + if (!self->finished_) { + // Note: request_router->lb_policy_ may have changed since we started our + // pick, in which case we will be cancelling the pick on a policy other + // than the one we started it on. However, this will just be a no-op. + if (error != GRPC_ERROR_NONE && request_router->lb_policy_ != nullptr) { + if (self->tracer_enabled_) { + gpr_log(GPR_INFO, + "request_router=%p request=%p: cancelling pick from LB " + "policy %p", + request_router, request, request_router->lb_policy_.get()); + } + request_router->lb_policy_->CancelPickLocked(&request->pick_, + GRPC_ERROR_REF(error)); + } + request->pick_canceller_ = nullptr; + GRPC_CALL_STACK_UNREF(request->owning_call_, "pick_callback_cancel"); + } + Delete(self); + } + + RequestRouter* request_router_; + Request* request_; + const bool tracer_enabled_; + grpc_closure cancel_closure_; + bool finished_ = false; +}; + +// +// RequestRouter::Request +// + +RequestRouter::Request::Request(grpc_call_stack* owning_call, + grpc_call_combiner* call_combiner, + grpc_polling_entity* pollent, + grpc_metadata_batch* send_initial_metadata, + uint32_t* send_initial_metadata_flags, + ApplyServiceConfigCallback apply_service_config, + void* apply_service_config_user_data, + grpc_closure* on_route_done) + : owning_call_(owning_call), + call_combiner_(call_combiner), + pollent_(pollent), + apply_service_config_(apply_service_config), + apply_service_config_user_data_(apply_service_config_user_data), + on_route_done_(on_route_done) { + pick_.initial_metadata = send_initial_metadata; + pick_.initial_metadata_flags = send_initial_metadata_flags; +} + +RequestRouter::Request::~Request() { + if (pick_.connected_subchannel != nullptr) { + pick_.connected_subchannel.reset(); + } + for (size_t i = 0; i < GRPC_CONTEXT_COUNT; ++i) { + if (pick_.subchannel_call_context[i].destroy != nullptr) { + pick_.subchannel_call_context[i].destroy( + pick_.subchannel_call_context[i].value); + } + } +} + +// Invoked once resolver results are available. +void RequestRouter::Request::ProcessServiceConfigAndStartLbPickLocked() { + // Get service config data if needed. + if (!apply_service_config_(apply_service_config_user_data_)) return; + // Start LB pick. + StartLbPickLocked(); +} + +void RequestRouter::Request::MaybeAddCallToInterestedPartiesLocked() { + if (!pollent_added_to_interested_parties_) { + pollent_added_to_interested_parties_ = true; + grpc_polling_entity_add_to_pollset_set( + pollent_, request_router_->interested_parties_); + } +} + +void RequestRouter::Request::MaybeRemoveCallFromInterestedPartiesLocked() { + if (pollent_added_to_interested_parties_) { + pollent_added_to_interested_parties_ = false; + grpc_polling_entity_del_from_pollset_set( + pollent_, request_router_->interested_parties_); + } +} + +// Starts a pick on the LB policy. +void RequestRouter::Request::StartLbPickLocked() { + if (request_router_->tracer_->enabled()) { + gpr_log(GPR_INFO, + "request_router=%p request=%p: starting pick on lb_policy=%p", + request_router_, this, request_router_->lb_policy_.get()); + } + GRPC_CLOSURE_INIT(&on_pick_done_, &LbPickDoneLocked, this, + grpc_combiner_scheduler(request_router_->combiner_)); + pick_.on_complete = &on_pick_done_; + GRPC_CALL_STACK_REF(owning_call_, "pick_callback"); + grpc_error* error = GRPC_ERROR_NONE; + const bool pick_done = + request_router_->lb_policy_->PickLocked(&pick_, &error); + if (pick_done) { + // Pick completed synchronously. + if (request_router_->tracer_->enabled()) { + gpr_log(GPR_INFO, + "request_router=%p request=%p: pick completed synchronously", + request_router_, this); + } + GRPC_CLOSURE_RUN(on_route_done_, error); + GRPC_CALL_STACK_UNREF(owning_call_, "pick_callback"); + } else { + // Pick will be returned asynchronously. + // Add the request's polling entity to the request_router's + // interested_parties, so that the I/O of the LB policy can be done + // under it. It will be removed in LbPickDoneLocked(). + MaybeAddCallToInterestedPartiesLocked(); + // Request notification on call cancellation. + // We allocate a separate object to track cancellation, since the + // cancellation closure might still be pending when we need to reuse + // the memory in which this Request object is stored for a subsequent + // retry attempt. + pick_canceller_ = New(this); + } +} + +// Callback invoked by LoadBalancingPolicy::PickLocked() for async picks. +// Unrefs the LB policy and invokes on_route_done_. +void RequestRouter::Request::LbPickDoneLocked(void* arg, grpc_error* error) { + Request* self = static_cast(arg); + RequestRouter* request_router = self->request_router_; + if (request_router->tracer_->enabled()) { + gpr_log(GPR_INFO, + "request_router=%p request=%p: pick completed asynchronously", + request_router, self); + } + self->MaybeRemoveCallFromInterestedPartiesLocked(); + if (self->pick_canceller_ != nullptr) { + self->pick_canceller_->MarkFinishedLocked(); + } + GRPC_CLOSURE_RUN(self->on_route_done_, GRPC_ERROR_REF(error)); + GRPC_CALL_STACK_UNREF(self->owning_call_, "pick_callback"); +} + +// +// RequestRouter::LbConnectivityWatcher +// + +class RequestRouter::LbConnectivityWatcher { + public: + LbConnectivityWatcher(RequestRouter* request_router, + grpc_connectivity_state state, + LoadBalancingPolicy* lb_policy, + grpc_channel_stack* owning_stack, + grpc_combiner* combiner) + : request_router_(request_router), + state_(state), + lb_policy_(lb_policy), + owning_stack_(owning_stack) { + GRPC_CHANNEL_STACK_REF(owning_stack_, "LbConnectivityWatcher"); + GRPC_CLOSURE_INIT(&on_changed_, &OnLbPolicyStateChangedLocked, this, + grpc_combiner_scheduler(combiner)); + lb_policy_->NotifyOnStateChangeLocked(&state_, &on_changed_); + } + + ~LbConnectivityWatcher() { + GRPC_CHANNEL_STACK_UNREF(owning_stack_, "LbConnectivityWatcher"); + } + + private: + static void OnLbPolicyStateChangedLocked(void* arg, grpc_error* error) { + LbConnectivityWatcher* self = static_cast(arg); + // If the notification is not for the current policy, we're stale, + // so delete ourselves. + if (self->lb_policy_ != self->request_router_->lb_policy_.get()) { + Delete(self); + return; + } + // Otherwise, process notification. + if (self->request_router_->tracer_->enabled()) { + gpr_log(GPR_INFO, "request_router=%p: lb_policy=%p state changed to %s", + self->request_router_, self->lb_policy_, + grpc_connectivity_state_name(self->state_)); + } + self->request_router_->SetConnectivityStateLocked( + self->state_, GRPC_ERROR_REF(error), "lb_changed"); + // If shutting down, terminate watch. + if (self->state_ == GRPC_CHANNEL_SHUTDOWN) { + Delete(self); + return; + } + // Renew watch. + self->lb_policy_->NotifyOnStateChangeLocked(&self->state_, + &self->on_changed_); + } + + RequestRouter* request_router_; + grpc_connectivity_state state_; + // LB policy address. No ref held, so not safe to dereference unless + // it happens to match request_router->lb_policy_. + LoadBalancingPolicy* lb_policy_; + grpc_channel_stack* owning_stack_; + grpc_closure on_changed_; +}; + +// +// RequestRounter::ReresolutionRequestHandler +// + +class RequestRouter::ReresolutionRequestHandler { + public: + ReresolutionRequestHandler(RequestRouter* request_router, + LoadBalancingPolicy* lb_policy, + grpc_channel_stack* owning_stack, + grpc_combiner* combiner) + : request_router_(request_router), + lb_policy_(lb_policy), + owning_stack_(owning_stack) { + GRPC_CHANNEL_STACK_REF(owning_stack_, "ReresolutionRequestHandler"); + GRPC_CLOSURE_INIT(&closure_, &OnRequestReresolutionLocked, this, + grpc_combiner_scheduler(combiner)); + lb_policy_->SetReresolutionClosureLocked(&closure_); + } + + private: + static void OnRequestReresolutionLocked(void* arg, grpc_error* error) { + ReresolutionRequestHandler* self = + static_cast(arg); + RequestRouter* request_router = self->request_router_; + // If this invocation is for a stale LB policy, treat it as an LB shutdown + // signal. + if (self->lb_policy_ != request_router->lb_policy_.get() || + error != GRPC_ERROR_NONE || request_router->resolver_ == nullptr) { + GRPC_CHANNEL_STACK_UNREF(request_router->owning_stack_, + "ReresolutionRequestHandler"); + Delete(self); + return; + } + if (request_router->tracer_->enabled()) { + gpr_log(GPR_INFO, "request_router=%p: started name re-resolving", + request_router); + } + request_router->resolver_->RequestReresolutionLocked(); + // Give back the closure to the LB policy. + self->lb_policy_->SetReresolutionClosureLocked(&self->closure_); + } + + RequestRouter* request_router_; + // LB policy address. No ref held, so not safe to dereference unless + // it happens to match request_router->lb_policy_. + LoadBalancingPolicy* lb_policy_; + grpc_channel_stack* owning_stack_; + grpc_closure closure_; +}; + +// +// RequestRouter +// + +RequestRouter::RequestRouter( + grpc_channel_stack* owning_stack, grpc_combiner* combiner, + grpc_client_channel_factory* client_channel_factory, + grpc_pollset_set* interested_parties, TraceFlag* tracer, + ProcessResolverResultCallback process_resolver_result, + void* process_resolver_result_user_data, const char* target_uri, + const grpc_channel_args* args, grpc_error** error) + : owning_stack_(owning_stack), + combiner_(combiner), + client_channel_factory_(client_channel_factory), + interested_parties_(interested_parties), + tracer_(tracer), + process_resolver_result_(process_resolver_result), + process_resolver_result_user_data_(process_resolver_result_user_data) { + // Get subchannel pool. + const grpc_arg* arg = + grpc_channel_args_find(args, GRPC_ARG_USE_LOCAL_SUBCHANNEL_POOL); + if (grpc_channel_arg_get_bool(arg, false)) { + subchannel_pool_ = MakeRefCounted(); + } else { + subchannel_pool_ = GlobalSubchannelPool::instance(); + } + GRPC_CLOSURE_INIT(&on_resolver_result_changed_, + &RequestRouter::OnResolverResultChangedLocked, this, + grpc_combiner_scheduler(combiner)); + grpc_connectivity_state_init(&state_tracker_, GRPC_CHANNEL_IDLE, + "request_router"); + grpc_channel_args* new_args = nullptr; + if (process_resolver_result == nullptr) { + grpc_arg arg = grpc_channel_arg_integer_create( + const_cast(GRPC_ARG_SERVICE_CONFIG_DISABLE_RESOLUTION), 0); + new_args = grpc_channel_args_copy_and_add(args, &arg, 1); + } + resolver_ = ResolverRegistry::CreateResolver( + target_uri, (new_args == nullptr ? args : new_args), interested_parties_, + combiner_); + grpc_channel_args_destroy(new_args); + if (resolver_ == nullptr) { + *error = GRPC_ERROR_CREATE_FROM_STATIC_STRING("resolver creation failed"); + } +} + +RequestRouter::~RequestRouter() { + if (resolver_ != nullptr) { + // The only way we can get here is if we never started resolving, + // because we take a ref to the channel stack when we start + // resolving and do not release it until the resolver callback is + // invoked after the resolver shuts down. + resolver_.reset(); + } + if (lb_policy_ != nullptr) { + grpc_pollset_set_del_pollset_set(lb_policy_->interested_parties(), + interested_parties_); + lb_policy_.reset(); + } + if (client_channel_factory_ != nullptr) { + grpc_client_channel_factory_unref(client_channel_factory_); + } + grpc_connectivity_state_destroy(&state_tracker_); +} + +namespace { + +const char* GetChannelConnectivityStateChangeString( + grpc_connectivity_state state) { + switch (state) { + case GRPC_CHANNEL_IDLE: + return "Channel state change to IDLE"; + case GRPC_CHANNEL_CONNECTING: + return "Channel state change to CONNECTING"; + case GRPC_CHANNEL_READY: + return "Channel state change to READY"; + case GRPC_CHANNEL_TRANSIENT_FAILURE: + return "Channel state change to TRANSIENT_FAILURE"; + case GRPC_CHANNEL_SHUTDOWN: + return "Channel state change to SHUTDOWN"; + } + GPR_UNREACHABLE_CODE(return "UNKNOWN"); +} + +} // namespace + +void RequestRouter::SetConnectivityStateLocked(grpc_connectivity_state state, + grpc_error* error, + const char* reason) { + if (lb_policy_ != nullptr) { + if (state == GRPC_CHANNEL_TRANSIENT_FAILURE) { + // Cancel picks with wait_for_ready=false. + lb_policy_->CancelMatchingPicksLocked( + /* mask= */ GRPC_INITIAL_METADATA_WAIT_FOR_READY, + /* check= */ 0, GRPC_ERROR_REF(error)); + } else if (state == GRPC_CHANNEL_SHUTDOWN) { + // Cancel all picks. + lb_policy_->CancelMatchingPicksLocked(/* mask= */ 0, /* check= */ 0, + GRPC_ERROR_REF(error)); + } + } + if (tracer_->enabled()) { + gpr_log(GPR_INFO, "request_router=%p: setting connectivity state to %s", + this, grpc_connectivity_state_name(state)); + } + if (channelz_node_ != nullptr) { + channelz_node_->AddTraceEvent( + channelz::ChannelTrace::Severity::Info, + grpc_slice_from_static_string( + GetChannelConnectivityStateChangeString(state))); + } + grpc_connectivity_state_set(&state_tracker_, state, error, reason); +} + +void RequestRouter::StartResolvingLocked() { + if (tracer_->enabled()) { + gpr_log(GPR_INFO, "request_router=%p: starting name resolution", this); + } + GPR_ASSERT(!started_resolving_); + started_resolving_ = true; + GRPC_CHANNEL_STACK_REF(owning_stack_, "resolver"); + resolver_->NextLocked(&resolver_result_, &on_resolver_result_changed_); +} + +// Invoked from the resolver NextLocked() callback when the resolver +// is shutting down. +void RequestRouter::OnResolverShutdownLocked(grpc_error* error) { + if (tracer_->enabled()) { + gpr_log(GPR_INFO, "request_router=%p: shutting down", this); + } + if (lb_policy_ != nullptr) { + if (tracer_->enabled()) { + gpr_log(GPR_INFO, "request_router=%p: shutting down lb_policy=%p", this, + lb_policy_.get()); + } + grpc_pollset_set_del_pollset_set(lb_policy_->interested_parties(), + interested_parties_); + lb_policy_.reset(); + } + if (resolver_ != nullptr) { + // This should never happen; it can only be triggered by a resolver + // implementation spotaneously deciding to report shutdown without + // being orphaned. This code is included just to be defensive. + if (tracer_->enabled()) { + gpr_log(GPR_INFO, + "request_router=%p: spontaneous shutdown from resolver %p", this, + resolver_.get()); + } + resolver_.reset(); + SetConnectivityStateLocked(GRPC_CHANNEL_SHUTDOWN, + GRPC_ERROR_CREATE_REFERENCING_FROM_STATIC_STRING( + "Resolver spontaneous shutdown", &error, 1), + "resolver_spontaneous_shutdown"); + } + grpc_closure_list_fail_all(&waiting_for_resolver_result_closures_, + GRPC_ERROR_CREATE_REFERENCING_FROM_STATIC_STRING( + "Channel disconnected", &error, 1)); + GRPC_CLOSURE_LIST_SCHED(&waiting_for_resolver_result_closures_); + GRPC_CHANNEL_STACK_UNREF(owning_stack_, "resolver"); + grpc_channel_args_destroy(resolver_result_); + resolver_result_ = nullptr; + GRPC_ERROR_UNREF(error); +} + +// Creates a new LB policy, replacing any previous one. +// If the new policy is created successfully, sets *connectivity_state and +// *connectivity_error to its initial connectivity state; otherwise, +// leaves them unchanged. +void RequestRouter::CreateNewLbPolicyLocked( + const char* lb_policy_name, grpc_json* lb_config, + grpc_connectivity_state* connectivity_state, + grpc_error** connectivity_error, TraceStringVector* trace_strings) { + LoadBalancingPolicy::Args lb_policy_args; + lb_policy_args.combiner = combiner_; + lb_policy_args.client_channel_factory = client_channel_factory_; + lb_policy_args.subchannel_pool = subchannel_pool_; + lb_policy_args.args = resolver_result_; + lb_policy_args.lb_config = lb_config; + OrphanablePtr new_lb_policy = + LoadBalancingPolicyRegistry::CreateLoadBalancingPolicy(lb_policy_name, + lb_policy_args); + if (GPR_UNLIKELY(new_lb_policy == nullptr)) { + gpr_log(GPR_ERROR, "could not create LB policy \"%s\"", lb_policy_name); + if (channelz_node_ != nullptr) { + char* str; + gpr_asprintf(&str, "Could not create LB policy \'%s\'", lb_policy_name); + trace_strings->push_back(str); + } + } else { + if (tracer_->enabled()) { + gpr_log(GPR_INFO, "request_router=%p: created new LB policy \"%s\" (%p)", + this, lb_policy_name, new_lb_policy.get()); + } + if (channelz_node_ != nullptr) { + char* str; + gpr_asprintf(&str, "Created new LB policy \'%s\'", lb_policy_name); + trace_strings->push_back(str); + } + // Swap out the LB policy and update the fds in interested_parties_. + if (lb_policy_ != nullptr) { + if (tracer_->enabled()) { + gpr_log(GPR_INFO, "request_router=%p: shutting down lb_policy=%p", this, + lb_policy_.get()); + } + grpc_pollset_set_del_pollset_set(lb_policy_->interested_parties(), + interested_parties_); + lb_policy_->HandOffPendingPicksLocked(new_lb_policy.get()); + } + lb_policy_ = std::move(new_lb_policy); + grpc_pollset_set_add_pollset_set(lb_policy_->interested_parties(), + interested_parties_); + // Create re-resolution request handler for the new LB policy. It + // will delete itself when no longer needed. + New(this, lb_policy_.get(), owning_stack_, + combiner_); + // Get the new LB policy's initial connectivity state and start a + // connectivity watch. + GRPC_ERROR_UNREF(*connectivity_error); + *connectivity_state = + lb_policy_->CheckConnectivityLocked(connectivity_error); + if (exit_idle_when_lb_policy_arrives_) { + lb_policy_->ExitIdleLocked(); + exit_idle_when_lb_policy_arrives_ = false; + } + // Create new watcher. It will delete itself when done. + New(this, *connectivity_state, lb_policy_.get(), + owning_stack_, combiner_); + } +} + +void RequestRouter::MaybeAddTraceMessagesForAddressChangesLocked( + TraceStringVector* trace_strings) { + const ServerAddressList* addresses = + FindServerAddressListChannelArg(resolver_result_); + const bool resolution_contains_addresses = + addresses != nullptr && addresses->size() > 0; + if (!resolution_contains_addresses && + previous_resolution_contained_addresses_) { + trace_strings->push_back(gpr_strdup("Address list became empty")); + } else if (resolution_contains_addresses && + !previous_resolution_contained_addresses_) { + trace_strings->push_back(gpr_strdup("Address list became non-empty")); + } + previous_resolution_contained_addresses_ = resolution_contains_addresses; +} + +void RequestRouter::ConcatenateAndAddChannelTraceLocked( + TraceStringVector* trace_strings) const { + if (!trace_strings->empty()) { + gpr_strvec v; + gpr_strvec_init(&v); + gpr_strvec_add(&v, gpr_strdup("Resolution event: ")); + bool is_first = 1; + for (size_t i = 0; i < trace_strings->size(); ++i) { + if (!is_first) gpr_strvec_add(&v, gpr_strdup(", ")); + is_first = false; + gpr_strvec_add(&v, (*trace_strings)[i]); + } + char* flat; + size_t flat_len = 0; + flat = gpr_strvec_flatten(&v, &flat_len); + channelz_node_->AddTraceEvent(channelz::ChannelTrace::Severity::Info, + grpc_slice_new(flat, flat_len, gpr_free)); + gpr_strvec_destroy(&v); + } +} + +// Callback invoked when a resolver result is available. +void RequestRouter::OnResolverResultChangedLocked(void* arg, + grpc_error* error) { + RequestRouter* self = static_cast(arg); + if (self->tracer_->enabled()) { + const char* disposition = + self->resolver_result_ != nullptr + ? "" + : (error == GRPC_ERROR_NONE ? " (transient error)" + : " (resolver shutdown)"); + gpr_log(GPR_INFO, + "request_router=%p: got resolver result: resolver_result=%p " + "error=%s%s", + self, self->resolver_result_, grpc_error_string(error), + disposition); + } + // Handle shutdown. + if (error != GRPC_ERROR_NONE || self->resolver_ == nullptr) { + self->OnResolverShutdownLocked(GRPC_ERROR_REF(error)); + return; + } + // Data used to set the channel's connectivity state. + bool set_connectivity_state = true; + // We only want to trace the address resolution in the follow cases: + // (a) Address resolution resulted in service config change. + // (b) Address resolution that causes number of backends to go from + // zero to non-zero. + // (c) Address resolution that causes number of backends to go from + // non-zero to zero. + // (d) Address resolution that causes a new LB policy to be created. + // + // we track a list of strings to eventually be concatenated and traced. + TraceStringVector trace_strings; + grpc_connectivity_state connectivity_state = GRPC_CHANNEL_TRANSIENT_FAILURE; + grpc_error* connectivity_error = + GRPC_ERROR_CREATE_FROM_STATIC_STRING("No load balancing policy"); + // resolver_result_ will be null in the case of a transient + // resolution error. In that case, we don't have any new result to + // process, which means that we keep using the previous result (if any). + if (self->resolver_result_ == nullptr) { + if (self->tracer_->enabled()) { + gpr_log(GPR_INFO, "request_router=%p: resolver transient failure", self); + } + // Don't override connectivity state if we already have an LB policy. + if (self->lb_policy_ != nullptr) set_connectivity_state = false; + } else { + // Parse the resolver result. + const char* lb_policy_name = nullptr; + grpc_json* lb_policy_config = nullptr; + const bool service_config_changed = self->process_resolver_result_( + self->process_resolver_result_user_data_, *self->resolver_result_, + &lb_policy_name, &lb_policy_config); + GPR_ASSERT(lb_policy_name != nullptr); + // Check to see if we're already using the right LB policy. + const bool lb_policy_name_changed = + self->lb_policy_ == nullptr || + strcmp(self->lb_policy_->name(), lb_policy_name) != 0; + if (self->lb_policy_ != nullptr && !lb_policy_name_changed) { + // Continue using the same LB policy. Update with new addresses. + if (self->tracer_->enabled()) { + gpr_log(GPR_INFO, + "request_router=%p: updating existing LB policy \"%s\" (%p)", + self, lb_policy_name, self->lb_policy_.get()); + } + self->lb_policy_->UpdateLocked(*self->resolver_result_, lb_policy_config); + // No need to set the channel's connectivity state; the existing + // watch on the LB policy will take care of that. + set_connectivity_state = false; + } else { + // Instantiate new LB policy. + self->CreateNewLbPolicyLocked(lb_policy_name, lb_policy_config, + &connectivity_state, &connectivity_error, + &trace_strings); + } + // Add channel trace event. + if (self->channelz_node_ != nullptr) { + if (service_config_changed) { + // TODO(ncteisen): might be worth somehow including a snippet of the + // config in the trace, at the risk of bloating the trace logs. + trace_strings.push_back(gpr_strdup("Service config changed")); + } + self->MaybeAddTraceMessagesForAddressChangesLocked(&trace_strings); + self->ConcatenateAndAddChannelTraceLocked(&trace_strings); + } + // Clean up. + grpc_channel_args_destroy(self->resolver_result_); + self->resolver_result_ = nullptr; + } + // Set the channel's connectivity state if needed. + if (set_connectivity_state) { + self->SetConnectivityStateLocked(connectivity_state, connectivity_error, + "resolver_result"); + } else { + GRPC_ERROR_UNREF(connectivity_error); + } + // Invoke closures that were waiting for results and renew the watch. + GRPC_CLOSURE_LIST_SCHED(&self->waiting_for_resolver_result_closures_); + self->resolver_->NextLocked(&self->resolver_result_, + &self->on_resolver_result_changed_); +} + +void RequestRouter::RouteCallLocked(Request* request) { + GPR_ASSERT(request->pick_.connected_subchannel == nullptr); + request->request_router_ = this; + if (lb_policy_ != nullptr) { + // We already have resolver results, so process the service config + // and start an LB pick. + request->ProcessServiceConfigAndStartLbPickLocked(); + } else if (resolver_ == nullptr) { + GRPC_CLOSURE_RUN(request->on_route_done_, + GRPC_ERROR_CREATE_FROM_STATIC_STRING("Disconnected")); + } else { + // We do not yet have an LB policy, so wait for a resolver result. + if (!started_resolving_) { + StartResolvingLocked(); + } + // Create a new waiter, which will delete itself when done. + New(request); + // Add the request's polling entity to the request_router's + // interested_parties, so that the I/O of the resolver can be done + // under it. It will be removed in LbPickDoneLocked(). + request->MaybeAddCallToInterestedPartiesLocked(); + } +} + +void RequestRouter::ShutdownLocked(grpc_error* error) { + if (resolver_ != nullptr) { + SetConnectivityStateLocked(GRPC_CHANNEL_SHUTDOWN, GRPC_ERROR_REF(error), + "disconnect"); + resolver_.reset(); + if (!started_resolving_) { + grpc_closure_list_fail_all(&waiting_for_resolver_result_closures_, + GRPC_ERROR_REF(error)); + GRPC_CLOSURE_LIST_SCHED(&waiting_for_resolver_result_closures_); + } + if (lb_policy_ != nullptr) { + grpc_pollset_set_del_pollset_set(lb_policy_->interested_parties(), + interested_parties_); + lb_policy_.reset(); + } + } + GRPC_ERROR_UNREF(error); +} + +grpc_connectivity_state RequestRouter::GetConnectivityState() { + return grpc_connectivity_state_check(&state_tracker_); +} + +void RequestRouter::NotifyOnConnectivityStateChange( + grpc_connectivity_state* state, grpc_closure* closure) { + grpc_connectivity_state_notify_on_state_change(&state_tracker_, state, + closure); +} + +void RequestRouter::ExitIdleLocked() { + if (lb_policy_ != nullptr) { + lb_policy_->ExitIdleLocked(); + } else { + exit_idle_when_lb_policy_arrives_ = true; + if (!started_resolving_ && resolver_ != nullptr) { + StartResolvingLocked(); + } + } +} + +void RequestRouter::ResetConnectionBackoffLocked() { + if (resolver_ != nullptr) { + resolver_->ResetBackoffLocked(); + resolver_->RequestReresolutionLocked(); + } + if (lb_policy_ != nullptr) { + lb_policy_->ResetBackoffLocked(); + } +} + +} // namespace grpc_core diff --git a/src/core/ext/filters/client_channel/request_routing.h b/src/core/ext/filters/client_channel/request_routing.h new file mode 100644 index 00000000000..0027163869e --- /dev/null +++ b/src/core/ext/filters/client_channel/request_routing.h @@ -0,0 +1,181 @@ +/* + * + * Copyright 2018 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. + * + */ + +#ifndef GRPC_CORE_EXT_FILTERS_CLIENT_CHANNEL_REQUEST_ROUTING_H +#define GRPC_CORE_EXT_FILTERS_CLIENT_CHANNEL_REQUEST_ROUTING_H + +#include + +#include "src/core/ext/filters/client_channel/client_channel_channelz.h" +#include "src/core/ext/filters/client_channel/client_channel_factory.h" +#include "src/core/ext/filters/client_channel/lb_policy.h" +#include "src/core/ext/filters/client_channel/resolver.h" +#include "src/core/ext/filters/client_channel/subchannel_pool_interface.h" +#include "src/core/lib/channel/channel_args.h" +#include "src/core/lib/channel/channel_stack.h" +#include "src/core/lib/debug/trace.h" +#include "src/core/lib/gprpp/inlined_vector.h" +#include "src/core/lib/gprpp/orphanable.h" +#include "src/core/lib/iomgr/call_combiner.h" +#include "src/core/lib/iomgr/closure.h" +#include "src/core/lib/iomgr/polling_entity.h" +#include "src/core/lib/iomgr/pollset_set.h" +#include "src/core/lib/transport/connectivity_state.h" +#include "src/core/lib/transport/metadata_batch.h" + +namespace grpc_core { + +class RequestRouter { + public: + class Request { + public: + // Synchronous callback that applies the service config to a call. + // Returns false if the call should be failed. + typedef bool (*ApplyServiceConfigCallback)(void* user_data); + + Request(grpc_call_stack* owning_call, grpc_call_combiner* call_combiner, + grpc_polling_entity* pollent, + grpc_metadata_batch* send_initial_metadata, + uint32_t* send_initial_metadata_flags, + ApplyServiceConfigCallback apply_service_config, + void* apply_service_config_user_data, grpc_closure* on_route_done); + + ~Request(); + + // TODO(roth): It seems a bit ugly to expose this member in a + // non-const way. Find a better API to avoid this. + LoadBalancingPolicy::PickState* pick() { return &pick_; } + + private: + friend class RequestRouter; + + class ResolverResultWaiter; + class AsyncPickCanceller; + + void ProcessServiceConfigAndStartLbPickLocked(); + void StartLbPickLocked(); + static void LbPickDoneLocked(void* arg, grpc_error* error); + + void MaybeAddCallToInterestedPartiesLocked(); + void MaybeRemoveCallFromInterestedPartiesLocked(); + + // Populated by caller. + grpc_call_stack* owning_call_; + grpc_call_combiner* call_combiner_; + grpc_polling_entity* pollent_; + ApplyServiceConfigCallback apply_service_config_; + void* apply_service_config_user_data_; + grpc_closure* on_route_done_; + LoadBalancingPolicy::PickState pick_; + + // Internal state. + RequestRouter* request_router_ = nullptr; + bool pollent_added_to_interested_parties_ = false; + grpc_closure on_pick_done_; + AsyncPickCanceller* pick_canceller_ = nullptr; + }; + + // Synchronous callback that takes the service config JSON string and + // LB policy name. + // Returns true if the service config has changed since the last result. + typedef bool (*ProcessResolverResultCallback)(void* user_data, + const grpc_channel_args& args, + const char** lb_policy_name, + grpc_json** lb_policy_config); + + RequestRouter(grpc_channel_stack* owning_stack, grpc_combiner* combiner, + grpc_client_channel_factory* client_channel_factory, + grpc_pollset_set* interested_parties, TraceFlag* tracer, + ProcessResolverResultCallback process_resolver_result, + void* process_resolver_result_user_data, const char* target_uri, + const grpc_channel_args* args, grpc_error** error); + + ~RequestRouter(); + + void set_channelz_node(channelz::ClientChannelNode* channelz_node) { + channelz_node_ = channelz_node; + } + + void RouteCallLocked(Request* request); + + // TODO(roth): Add methods to cancel picks. + + void ShutdownLocked(grpc_error* error); + + void ExitIdleLocked(); + void ResetConnectionBackoffLocked(); + + grpc_connectivity_state GetConnectivityState(); + void NotifyOnConnectivityStateChange(grpc_connectivity_state* state, + grpc_closure* closure); + + LoadBalancingPolicy* lb_policy() const { return lb_policy_.get(); } + + private: + using TraceStringVector = InlinedVector; + + class ReresolutionRequestHandler; + class LbConnectivityWatcher; + + void StartResolvingLocked(); + void OnResolverShutdownLocked(grpc_error* error); + void CreateNewLbPolicyLocked(const char* lb_policy_name, grpc_json* lb_config, + grpc_connectivity_state* connectivity_state, + grpc_error** connectivity_error, + TraceStringVector* trace_strings); + void MaybeAddTraceMessagesForAddressChangesLocked( + TraceStringVector* trace_strings); + void ConcatenateAndAddChannelTraceLocked( + TraceStringVector* trace_strings) const; + static void OnResolverResultChangedLocked(void* arg, grpc_error* error); + + void SetConnectivityStateLocked(grpc_connectivity_state state, + grpc_error* error, const char* reason); + + // Passed in from caller at construction time. + grpc_channel_stack* owning_stack_; + grpc_combiner* combiner_; + grpc_client_channel_factory* client_channel_factory_; + grpc_pollset_set* interested_parties_; + TraceFlag* tracer_; + + channelz::ClientChannelNode* channelz_node_ = nullptr; + + // Resolver and associated state. + OrphanablePtr resolver_; + ProcessResolverResultCallback process_resolver_result_; + void* process_resolver_result_user_data_; + bool started_resolving_ = false; + grpc_channel_args* resolver_result_ = nullptr; + bool previous_resolution_contained_addresses_ = false; + grpc_closure_list waiting_for_resolver_result_closures_; + grpc_closure on_resolver_result_changed_; + + // LB policy and associated state. + OrphanablePtr lb_policy_; + bool exit_idle_when_lb_policy_arrives_ = false; + + // Subchannel pool to pass to LB policy. + RefCountedPtr subchannel_pool_; + + grpc_connectivity_state_tracker state_tracker_; +}; + +} // namespace grpc_core + +#endif /* GRPC_CORE_EXT_FILTERS_CLIENT_CHANNEL_REQUEST_ROUTING_H */ diff --git a/src/core/ext/filters/client_channel/resolving_lb_policy.cc b/src/core/ext/filters/client_channel/resolving_lb_policy.cc deleted file mode 100644 index ad9720fdda9..00000000000 --- a/src/core/ext/filters/client_channel/resolving_lb_policy.cc +++ /dev/null @@ -1,460 +0,0 @@ -/* - * - * Copyright 2015 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. - * - */ - -#include - -#include "src/core/ext/filters/client_channel/resolving_lb_policy.h" - -#include -#include -#include -#include -#include - -#include -#include -#include -#include - -#include "src/core/ext/filters/client_channel/backup_poller.h" -#include "src/core/ext/filters/client_channel/http_connect_handshaker.h" -#include "src/core/ext/filters/client_channel/lb_policy_registry.h" -#include "src/core/ext/filters/client_channel/proxy_mapper_registry.h" -#include "src/core/ext/filters/client_channel/resolver_registry.h" -#include "src/core/ext/filters/client_channel/retry_throttle.h" -#include "src/core/ext/filters/client_channel/server_address.h" -#include "src/core/ext/filters/client_channel/subchannel.h" -#include "src/core/ext/filters/deadline/deadline_filter.h" -#include "src/core/lib/backoff/backoff.h" -#include "src/core/lib/channel/channel_args.h" -#include "src/core/lib/channel/connected_channel.h" -#include "src/core/lib/channel/status_util.h" -#include "src/core/lib/gpr/string.h" -#include "src/core/lib/gprpp/inlined_vector.h" -#include "src/core/lib/gprpp/manual_constructor.h" -#include "src/core/lib/iomgr/combiner.h" -#include "src/core/lib/iomgr/iomgr.h" -#include "src/core/lib/iomgr/polling_entity.h" -#include "src/core/lib/profiling/timers.h" -#include "src/core/lib/slice/slice_internal.h" -#include "src/core/lib/slice/slice_string_helpers.h" -#include "src/core/lib/surface/channel.h" -#include "src/core/lib/transport/connectivity_state.h" -#include "src/core/lib/transport/error_utils.h" -#include "src/core/lib/transport/metadata.h" -#include "src/core/lib/transport/metadata_batch.h" -#include "src/core/lib/transport/service_config.h" -#include "src/core/lib/transport/static_metadata.h" -#include "src/core/lib/transport/status_metadata.h" - -namespace grpc_core { - -// -// ResolvingLoadBalancingPolicy::ResolvingControlHelper -// - -class ResolvingLoadBalancingPolicy::ResolvingControlHelper - : public LoadBalancingPolicy::ChannelControlHelper { - public: - explicit ResolvingControlHelper( - RefCountedPtr parent) - : parent_(std::move(parent)) {} - - Subchannel* CreateSubchannel(const grpc_channel_args& args) override { - if (parent_->resolver_ == nullptr) return nullptr; // Shutting down. - return parent_->channel_control_helper()->CreateSubchannel(args); - } - - grpc_channel* CreateChannel(const char* target, grpc_client_channel_type type, - const grpc_channel_args& args) override { - if (parent_->resolver_ == nullptr) return nullptr; // Shutting down. - return parent_->channel_control_helper()->CreateChannel(target, type, args); - } - - void UpdateState(grpc_connectivity_state state, grpc_error* state_error, - UniquePtr picker) override { - if (parent_->resolver_ == nullptr) { - // shutting down. - GRPC_ERROR_UNREF(state_error); - return; - } - parent_->channel_control_helper()->UpdateState(state, state_error, - std::move(picker)); - } - - void RequestReresolution() override { - if (parent_->tracer_->enabled()) { - gpr_log(GPR_INFO, "resolving_lb=%p: started name re-resolving", - parent_.get()); - } - if (parent_->resolver_ != nullptr) { - parent_->resolver_->RequestReresolutionLocked(); - } - } - - private: - RefCountedPtr parent_; -}; - -// -// ResolvingLoadBalancingPolicy -// - -ResolvingLoadBalancingPolicy::ResolvingLoadBalancingPolicy( - Args args, TraceFlag* tracer, UniquePtr target_uri, - UniquePtr child_policy_name, grpc_json* child_lb_config, - grpc_error** error) - : LoadBalancingPolicy(std::move(args)), - tracer_(tracer), - target_uri_(std::move(target_uri)), - child_policy_name_(std::move(child_policy_name)), - child_lb_config_str_(grpc_json_dump_to_string(child_lb_config, 0)), - child_lb_config_(grpc_json_parse_string(child_lb_config_str_.get())) { - GPR_ASSERT(child_policy_name_ != nullptr); - // Don't fetch service config, since this ctor is for use in nested LB - // policies, not at the top level, and we only fetch the service - // config at the top level. - grpc_arg arg = grpc_channel_arg_integer_create( - const_cast(GRPC_ARG_SERVICE_CONFIG_DISABLE_RESOLUTION), 0); - grpc_channel_args* new_args = - grpc_channel_args_copy_and_add(args.args, &arg, 1); - *error = Init(*new_args); - grpc_channel_args_destroy(new_args); -} - -ResolvingLoadBalancingPolicy::ResolvingLoadBalancingPolicy( - Args args, TraceFlag* tracer, UniquePtr target_uri, - ProcessResolverResultCallback process_resolver_result, - void* process_resolver_result_user_data, grpc_error** error) - : LoadBalancingPolicy(std::move(args)), - tracer_(tracer), - target_uri_(std::move(target_uri)), - process_resolver_result_(process_resolver_result), - process_resolver_result_user_data_(process_resolver_result_user_data) { - GPR_ASSERT(process_resolver_result != nullptr); - *error = Init(*args.args); -} - -grpc_error* ResolvingLoadBalancingPolicy::Init(const grpc_channel_args& args) { - GRPC_CLOSURE_INIT( - &on_resolver_result_changed_, - &ResolvingLoadBalancingPolicy::OnResolverResultChangedLocked, this, - grpc_combiner_scheduler(combiner())); - resolver_ = ResolverRegistry::CreateResolver( - target_uri_.get(), &args, interested_parties(), combiner()); - if (resolver_ == nullptr) { - return GRPC_ERROR_CREATE_FROM_STATIC_STRING("resolver creation failed"); - } - // Return our picker to the channel. - channel_control_helper()->UpdateState( - GRPC_CHANNEL_IDLE, GRPC_ERROR_NONE, - UniquePtr(New(Ref()))); - return GRPC_ERROR_NONE; -} - -ResolvingLoadBalancingPolicy::~ResolvingLoadBalancingPolicy() { - GPR_ASSERT(resolver_ == nullptr); - GPR_ASSERT(lb_policy_ == nullptr); - grpc_json_destroy(child_lb_config_); -} - -void ResolvingLoadBalancingPolicy::ShutdownLocked() { - if (resolver_ != nullptr) { - resolver_.reset(); - if (lb_policy_ != nullptr) { - grpc_pollset_set_del_pollset_set(lb_policy_->interested_parties(), - interested_parties()); - lb_policy_.reset(); - } - } -} - -void ResolvingLoadBalancingPolicy::ExitIdleLocked() { - if (lb_policy_ != nullptr) { - lb_policy_->ExitIdleLocked(); - } else { - if (!started_resolving_ && resolver_ != nullptr) { - StartResolvingLocked(); - } - } -} - -void ResolvingLoadBalancingPolicy::ResetBackoffLocked() { - if (resolver_ != nullptr) { - resolver_->ResetBackoffLocked(); - resolver_->RequestReresolutionLocked(); - } - if (lb_policy_ != nullptr) { - lb_policy_->ResetBackoffLocked(); - } -} - -void ResolvingLoadBalancingPolicy::FillChildRefsForChannelz( - channelz::ChildRefsList* child_subchannels, - channelz::ChildRefsList* child_channels) { - if (lb_policy_ != nullptr) { - lb_policy_->FillChildRefsForChannelz(child_subchannels, child_channels); - } -} - -void ResolvingLoadBalancingPolicy::StartResolvingLocked() { - if (tracer_->enabled()) { - gpr_log(GPR_INFO, "resolving_lb=%p: starting name resolution", this); - } - GPR_ASSERT(!started_resolving_); - started_resolving_ = true; - Ref().release(); - resolver_->NextLocked(&resolver_result_, &on_resolver_result_changed_); -} - -// Invoked from the resolver NextLocked() callback when the resolver -// is shutting down. -void ResolvingLoadBalancingPolicy::OnResolverShutdownLocked(grpc_error* error) { - if (tracer_->enabled()) { - gpr_log(GPR_INFO, "resolving_lb=%p: shutting down", this); - } - if (lb_policy_ != nullptr) { - if (tracer_->enabled()) { - gpr_log(GPR_INFO, "resolving_lb=%p: shutting down lb_policy=%p", this, - lb_policy_.get()); - } - grpc_pollset_set_del_pollset_set(lb_policy_->interested_parties(), - interested_parties()); - lb_policy_.reset(); - } - if (resolver_ != nullptr) { - // This should never happen; it can only be triggered by a resolver - // implementation spotaneously deciding to report shutdown without - // being orphaned. This code is included just to be defensive. - if (tracer_->enabled()) { - gpr_log(GPR_INFO, - "resolving_lb=%p: spontaneous shutdown from resolver %p", this, - resolver_.get()); - } - resolver_.reset(); - grpc_error* error = GRPC_ERROR_CREATE_REFERENCING_FROM_STATIC_STRING( - "Resolver spontaneous shutdown", &error, 1); - channel_control_helper()->UpdateState( - GRPC_CHANNEL_SHUTDOWN, GRPC_ERROR_REF(error), - UniquePtr(New(error))); - } - grpc_channel_args_destroy(resolver_result_); - resolver_result_ = nullptr; - GRPC_ERROR_UNREF(error); - Unref(); -} - -// Creates a new LB policy, replacing any previous one. -// Updates trace_strings to indicate what was done. -void ResolvingLoadBalancingPolicy::CreateNewLbPolicyLocked( - const char* lb_policy_name, grpc_json* lb_config, - TraceStringVector* trace_strings) { - LoadBalancingPolicy::Args lb_policy_args; - lb_policy_args.combiner = combiner(); - lb_policy_args.channel_control_helper = - UniquePtr(New(Ref())); - lb_policy_args.args = resolver_result_; - lb_policy_args.lb_config = lb_config; - OrphanablePtr new_lb_policy = - LoadBalancingPolicyRegistry::CreateLoadBalancingPolicy( - lb_policy_name, std::move(lb_policy_args)); - if (GPR_UNLIKELY(new_lb_policy == nullptr)) { - gpr_log(GPR_ERROR, "could not create LB policy \"%s\"", lb_policy_name); - if (channelz_node() != nullptr) { - char* str; - gpr_asprintf(&str, "Could not create LB policy \"%s\"", lb_policy_name); - trace_strings->push_back(str); - } - } else { - if (tracer_->enabled()) { - gpr_log(GPR_INFO, "resolving_lb=%p: created new LB policy \"%s\" (%p)", - this, lb_policy_name, new_lb_policy.get()); - } - if (channelz_node() != nullptr) { - char* str; - gpr_asprintf(&str, "Created new LB policy \"%s\"", lb_policy_name); - trace_strings->push_back(str); - } - // Propagate channelz node. - auto* channelz = channelz_node(); - if (channelz != nullptr) { - new_lb_policy->set_channelz_node(channelz->Ref()); - } - // Swap out the LB policy and update the fds in interested_parties_. - if (lb_policy_ != nullptr) { - if (tracer_->enabled()) { - gpr_log(GPR_INFO, "resolving_lb=%p: shutting down lb_policy=%p", this, - lb_policy_.get()); - } - grpc_pollset_set_del_pollset_set(lb_policy_->interested_parties(), - interested_parties()); - } - lb_policy_ = std::move(new_lb_policy); - grpc_pollset_set_add_pollset_set(lb_policy_->interested_parties(), - interested_parties()); - lb_policy_->ExitIdleLocked(); - } -} - -void ResolvingLoadBalancingPolicy::MaybeAddTraceMessagesForAddressChangesLocked( - TraceStringVector* trace_strings) { - const ServerAddressList* addresses = - FindServerAddressListChannelArg(resolver_result_); - const bool resolution_contains_addresses = - addresses != nullptr && addresses->size() > 0; - if (!resolution_contains_addresses && - previous_resolution_contained_addresses_) { - trace_strings->push_back(gpr_strdup("Address list became empty")); - } else if (resolution_contains_addresses && - !previous_resolution_contained_addresses_) { - trace_strings->push_back(gpr_strdup("Address list became non-empty")); - } - previous_resolution_contained_addresses_ = resolution_contains_addresses; -} - -void ResolvingLoadBalancingPolicy::ConcatenateAndAddChannelTraceLocked( - TraceStringVector* trace_strings) const { - if (!trace_strings->empty()) { - gpr_strvec v; - gpr_strvec_init(&v); - gpr_strvec_add(&v, gpr_strdup("Resolution event: ")); - bool is_first = 1; - for (size_t i = 0; i < trace_strings->size(); ++i) { - if (!is_first) gpr_strvec_add(&v, gpr_strdup(", ")); - is_first = false; - gpr_strvec_add(&v, (*trace_strings)[i]); - } - char* flat; - size_t flat_len = 0; - flat = gpr_strvec_flatten(&v, &flat_len); - channelz_node()->AddTraceEvent(channelz::ChannelTrace::Severity::Info, - grpc_slice_new(flat, flat_len, gpr_free)); - gpr_strvec_destroy(&v); - } -} - -// Callback invoked when a resolver result is available. -void ResolvingLoadBalancingPolicy::OnResolverResultChangedLocked( - void* arg, grpc_error* error) { - auto* self = static_cast(arg); - if (self->tracer_->enabled()) { - const char* disposition = - self->resolver_result_ != nullptr - ? "" - : (error == GRPC_ERROR_NONE ? " (transient error)" - : " (resolver shutdown)"); - gpr_log(GPR_INFO, - "resolving_lb=%p: got resolver result: resolver_result=%p " - "error=%s%s", - self, self->resolver_result_, grpc_error_string(error), - disposition); - } - // Handle shutdown. - if (error != GRPC_ERROR_NONE || self->resolver_ == nullptr) { - self->OnResolverShutdownLocked(GRPC_ERROR_REF(error)); - return; - } - // We only want to trace the address resolution in the follow cases: - // (a) Address resolution resulted in service config change. - // (b) Address resolution that causes number of backends to go from - // zero to non-zero. - // (c) Address resolution that causes number of backends to go from - // non-zero to zero. - // (d) Address resolution that causes a new LB policy to be created. - // - // we track a list of strings to eventually be concatenated and traced. - TraceStringVector trace_strings; - // resolver_result_ will be null in the case of a transient - // resolution error. In that case, we don't have any new result to - // process, which means that we keep using the previous result (if any). - if (self->resolver_result_ == nullptr) { - if (self->tracer_->enabled()) { - gpr_log(GPR_INFO, "resolving_lb=%p: resolver transient failure", self); - } - // If we already have an LB policy from a previous resolution - // result, then we continue to let it set the connectivity state. - // Otherwise, we go into TRANSIENT_FAILURE. - if (self->lb_policy_ == nullptr) { - // TODO(roth): When we change the resolver API to be able to - // return transient errors in a cleaner way, we should make it the - // resolver's responsibility to attach a status to the error, - // rather than doing it centrally here. - grpc_error* state_error = grpc_error_set_int( - GRPC_ERROR_CREATE_REFERENCING_FROM_STATIC_STRING( - "Resolver transient failure", &error, 1), - GRPC_ERROR_INT_GRPC_STATUS, GRPC_STATUS_UNAVAILABLE); - self->channel_control_helper()->UpdateState( - GRPC_CHANNEL_TRANSIENT_FAILURE, GRPC_ERROR_REF(state_error), - UniquePtr( - New(state_error))); - } - } else { - // Parse the resolver result. - const char* lb_policy_name = nullptr; - grpc_json* lb_policy_config = nullptr; - bool service_config_changed = false; - if (self->process_resolver_result_ != nullptr) { - service_config_changed = self->process_resolver_result_( - self->process_resolver_result_user_data_, *self->resolver_result_, - &lb_policy_name, &lb_policy_config); - } else { - lb_policy_name = self->child_policy_name_.get(); - lb_policy_config = self->child_lb_config_; - } - GPR_ASSERT(lb_policy_name != nullptr); - // Check to see if we're already using the right LB policy. - const bool lb_policy_name_changed = - self->lb_policy_ == nullptr || - strcmp(self->lb_policy_->name(), lb_policy_name) != 0; - if (self->lb_policy_ != nullptr && !lb_policy_name_changed) { - // Continue using the same LB policy. Update with new addresses. - if (self->tracer_->enabled()) { - gpr_log(GPR_INFO, - "resolving_lb=%p: updating existing LB policy \"%s\" (%p)", - self, lb_policy_name, self->lb_policy_.get()); - } - self->lb_policy_->UpdateLocked(*self->resolver_result_, lb_policy_config); - } else { - // Instantiate new LB policy. - if (self->tracer_->enabled()) { - gpr_log(GPR_INFO, "resolving_lb=%p: creating new LB policy \"%s\"", - self, lb_policy_name); - } - self->CreateNewLbPolicyLocked(lb_policy_name, lb_policy_config, - &trace_strings); - } - // Add channel trace event. - if (self->channelz_node() != nullptr) { - if (service_config_changed) { - // TODO(ncteisen): might be worth somehow including a snippet of the - // config in the trace, at the risk of bloating the trace logs. - trace_strings.push_back(gpr_strdup("Service config changed")); - } - self->MaybeAddTraceMessagesForAddressChangesLocked(&trace_strings); - self->ConcatenateAndAddChannelTraceLocked(&trace_strings); - } - // Clean up. - grpc_channel_args_destroy(self->resolver_result_); - self->resolver_result_ = nullptr; - } - // Renew resolver callback. - self->resolver_->NextLocked(&self->resolver_result_, - &self->on_resolver_result_changed_); -} - -} // namespace grpc_core diff --git a/src/core/ext/filters/client_channel/resolving_lb_policy.h b/src/core/ext/filters/client_channel/resolving_lb_policy.h deleted file mode 100644 index c302ae5d975..00000000000 --- a/src/core/ext/filters/client_channel/resolving_lb_policy.h +++ /dev/null @@ -1,137 +0,0 @@ -/* - * - * Copyright 2018 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. - * - */ - -#ifndef GRPC_CORE_EXT_FILTERS_CLIENT_CHANNEL_RESOLVING_LB_POLICY_H -#define GRPC_CORE_EXT_FILTERS_CLIENT_CHANNEL_RESOLVING_LB_POLICY_H - -#include - -#include "src/core/ext/filters/client_channel/client_channel_channelz.h" -#include "src/core/ext/filters/client_channel/lb_policy.h" -#include "src/core/ext/filters/client_channel/resolver.h" -#include "src/core/lib/channel/channel_args.h" -#include "src/core/lib/channel/channel_stack.h" -#include "src/core/lib/debug/trace.h" -#include "src/core/lib/gprpp/inlined_vector.h" -#include "src/core/lib/gprpp/orphanable.h" -#include "src/core/lib/iomgr/call_combiner.h" -#include "src/core/lib/iomgr/closure.h" -#include "src/core/lib/iomgr/polling_entity.h" -#include "src/core/lib/iomgr/pollset_set.h" -#include "src/core/lib/transport/connectivity_state.h" -#include "src/core/lib/transport/metadata_batch.h" - -namespace grpc_core { - -// An LB policy that wraps a resolver and a child LB policy to make use -// of the addresses returned by the resolver. -// -// When used in the client_channel code, the resolver will attempt to -// fetch the service config, and the child LB policy name and config -// will be determined based on the service config. -// -// When used in an LB policy implementation that needs to do another -// round of resolution before creating a child policy, the resolver does -// not fetch the service config, and the caller must pre-determine the -// child LB policy and config to use. -class ResolvingLoadBalancingPolicy : public LoadBalancingPolicy { - public: - // If error is set when this returns, then construction failed, and - // the caller may not use the new object. - ResolvingLoadBalancingPolicy(Args args, TraceFlag* tracer, - UniquePtr target_uri, - UniquePtr child_policy_name, - grpc_json* child_lb_config, grpc_error** error); - - // Private ctor, to be used by client_channel only! - // - // Synchronous callback that takes the resolver result and sets - // lb_policy_name and lb_policy_config to point to the right data. - // Returns true if the service config has changed since the last result. - typedef bool (*ProcessResolverResultCallback)(void* user_data, - const grpc_channel_args& args, - const char** lb_policy_name, - grpc_json** lb_policy_config); - // If error is set when this returns, then construction failed, and - // the caller may not use the new object. - ResolvingLoadBalancingPolicy( - Args args, TraceFlag* tracer, UniquePtr target_uri, - ProcessResolverResultCallback process_resolver_result, - void* process_resolver_result_user_data, grpc_error** error); - - virtual const char* name() const override { return "resolving_lb"; } - - // No-op -- should never get updates from the channel. - // TODO(roth): Need to support updating child LB policy's config. - // For xds policy, will also need to support updating config - // independently of args from resolver, since they will be coming from - // different places. Maybe change LB policy API to support that? - void UpdateLocked(const grpc_channel_args& args, - grpc_json* lb_config) override {} - - void ExitIdleLocked() override; - - void ResetBackoffLocked() override; - - void FillChildRefsForChannelz( - channelz::ChildRefsList* child_subchannels, - channelz::ChildRefsList* child_channels) override; - - private: - using TraceStringVector = InlinedVector; - - class ResolvingControlHelper; - - ~ResolvingLoadBalancingPolicy(); - - grpc_error* Init(const grpc_channel_args& args); - void ShutdownLocked() override; - - void StartResolvingLocked(); - void OnResolverShutdownLocked(grpc_error* error); - void CreateNewLbPolicyLocked(const char* lb_policy_name, grpc_json* lb_config, - TraceStringVector* trace_strings); - void MaybeAddTraceMessagesForAddressChangesLocked( - TraceStringVector* trace_strings); - void ConcatenateAndAddChannelTraceLocked( - TraceStringVector* trace_strings) const; - static void OnResolverResultChangedLocked(void* arg, grpc_error* error); - - // Passed in from caller at construction time. - TraceFlag* tracer_; - UniquePtr target_uri_; - ProcessResolverResultCallback process_resolver_result_ = nullptr; - void* process_resolver_result_user_data_ = nullptr; - UniquePtr child_policy_name_; - UniquePtr child_lb_config_str_; - grpc_json* child_lb_config_ = nullptr; - - // Resolver and associated state. - OrphanablePtr resolver_; - bool started_resolving_ = false; - grpc_channel_args* resolver_result_ = nullptr; - bool previous_resolution_contained_addresses_ = false; - grpc_closure on_resolver_result_changed_; - - // Child LB policy and associated state. - OrphanablePtr lb_policy_; -}; - -} // namespace grpc_core - -#endif /* GRPC_CORE_EXT_FILTERS_CLIENT_CHANNEL_RESOLVING_LB_POLICY_H */ diff --git a/src/core/ext/filters/client_channel/subchannel.cc b/src/core/ext/filters/client_channel/subchannel.cc index e2e19a32fd6..1a07edad09c 100644 --- a/src/core/ext/filters/client_channel/subchannel.cc +++ b/src/core/ext/filters/client_channel/subchannel.cc @@ -956,17 +956,22 @@ void Subchannel::OnConnectingFinished(void* arg, grpc_error* error) { } else if (c->disconnected_) { GRPC_SUBCHANNEL_WEAK_UNREF(c, "connecting"); } else { - const char* errmsg = grpc_error_string(error); - gpr_log(GPR_INFO, "Connect failed: %s", errmsg); - error = + c->SetConnectivityStateLocked( + GRPC_CHANNEL_TRANSIENT_FAILURE, + grpc_error_set_int(GRPC_ERROR_CREATE_REFERENCING_FROM_STATIC_STRING( + "Connect Failed", &error, 1), + GRPC_ERROR_INT_GRPC_STATUS, GRPC_STATUS_UNAVAILABLE), + "connect_failed"); + grpc_connectivity_state_set( + &c->state_and_health_tracker_, GRPC_CHANNEL_TRANSIENT_FAILURE, grpc_error_set_int(GRPC_ERROR_CREATE_REFERENCING_FROM_STATIC_STRING( "Connect Failed", &error, 1), - GRPC_ERROR_INT_GRPC_STATUS, GRPC_STATUS_UNAVAILABLE); - c->SetConnectivityStateLocked(GRPC_CHANNEL_TRANSIENT_FAILURE, - GRPC_ERROR_REF(error), "connect_failed"); - grpc_connectivity_state_set(&c->state_and_health_tracker_, - GRPC_CHANNEL_TRANSIENT_FAILURE, error, - "connect_failed"); + GRPC_ERROR_INT_GRPC_STATUS, GRPC_STATUS_UNAVAILABLE), + "connect_failed"); + + const char* errmsg = grpc_error_string(error); + gpr_log(GPR_INFO, "Connect failed: %s", errmsg); + c->MaybeStartConnectingLocked(); GRPC_SUBCHANNEL_WEAK_UNREF(c, "connecting"); } diff --git a/src/core/lib/gprpp/orphanable.h b/src/core/lib/gprpp/orphanable.h index dda5026cbca..9053c60111f 100644 --- a/src/core/lib/gprpp/orphanable.h +++ b/src/core/lib/gprpp/orphanable.h @@ -94,9 +94,8 @@ class InternallyRefCounted : public Orphanable { // Note: RefCount tracing is only enabled on debug builds, even when a // TraceFlag is used. template - explicit InternallyRefCounted(TraceFlagT* trace_flag = nullptr, - intptr_t initial_refcount = 1) - : refs_(initial_refcount, trace_flag) {} + explicit InternallyRefCounted(TraceFlagT* trace_flag = nullptr) + : refs_(1, trace_flag) {} virtual ~InternallyRefCounted() = default; RefCountedPtr Ref() GRPC_MUST_USE_RESULT { diff --git a/src/core/lib/gprpp/ref_counted.h b/src/core/lib/gprpp/ref_counted.h index 761b77baf58..fa97ffcfed2 100644 --- a/src/core/lib/gprpp/ref_counted.h +++ b/src/core/lib/gprpp/ref_counted.h @@ -221,9 +221,8 @@ class RefCounted : public Impl { // Note: RefCount tracing is only enabled on debug builds, even when a // TraceFlag is used. template - explicit RefCounted(TraceFlagT* trace_flag = nullptr, - intptr_t initial_refcount = 1) - : refs_(initial_refcount, trace_flag) {} + explicit RefCounted(TraceFlagT* trace_flag = nullptr) + : refs_(1, trace_flag) {} // Note: Depending on the Impl used, this dtor can be implicitly virtual. ~RefCounted() = default; diff --git a/src/python/grpcio/grpc_core_dependencies.py b/src/python/grpcio/grpc_core_dependencies.py index a9d045281ec..71de0c4abe0 100644 --- a/src/python/grpcio/grpc_core_dependencies.py +++ b/src/python/grpcio/grpc_core_dependencies.py @@ -329,10 +329,10 @@ CORE_SOURCE_FILES = [ 'src/core/ext/filters/client_channel/parse_address.cc', 'src/core/ext/filters/client_channel/proxy_mapper.cc', 'src/core/ext/filters/client_channel/proxy_mapper_registry.cc', + 'src/core/ext/filters/client_channel/request_routing.cc', 'src/core/ext/filters/client_channel/resolver.cc', 'src/core/ext/filters/client_channel/resolver_registry.cc', 'src/core/ext/filters/client_channel/resolver_result_parsing.cc', - 'src/core/ext/filters/client_channel/resolving_lb_policy.cc', 'src/core/ext/filters/client_channel/retry_throttle.cc', 'src/core/ext/filters/client_channel/server_address.cc', 'src/core/ext/filters/client_channel/subchannel.cc', diff --git a/test/core/channel/channel_stack_builder_test.cc b/test/core/channel/channel_stack_builder_test.cc index efe616ab7fd..b5598e63f9f 100644 --- a/test/core/channel/channel_stack_builder_test.cc +++ b/test/core/channel/channel_stack_builder_test.cc @@ -45,6 +45,16 @@ static void call_destroy_func(grpc_call_element* elem, const grpc_call_final_info* final_info, grpc_closure* ignored) {} +static void call_func(grpc_call_element* elem, + grpc_transport_stream_op_batch* op) {} + +static void channel_func(grpc_channel_element* elem, grpc_transport_op* op) { + if (op->disconnect_with_error != GRPC_ERROR_NONE) { + GRPC_ERROR_UNREF(op->disconnect_with_error); + } + GRPC_CLOSURE_SCHED(op->on_consumed, GRPC_ERROR_NONE); +} + bool g_replacement_fn_called = false; bool g_original_fn_called = false; void set_arg_once_fn(grpc_channel_stack* channel_stack, @@ -67,8 +77,8 @@ static void test_channel_stack_builder_filter_replace(void) { } const grpc_channel_filter replacement_filter = { - grpc_call_next_op, - grpc_channel_next_op, + call_func, + channel_func, 0, call_init_func, grpc_call_stack_ignore_set_pollset_or_pollset_set, @@ -80,8 +90,8 @@ const grpc_channel_filter replacement_filter = { "filter_name"}; const grpc_channel_filter original_filter = { - grpc_call_next_op, - grpc_channel_next_op, + call_func, + channel_func, 0, call_init_func, grpc_call_stack_ignore_set_pollset_or_pollset_set, diff --git a/test/core/util/test_lb_policies.cc b/test/core/util/test_lb_policies.cc index 77b354740e5..d6d072101ac 100644 --- a/test/core/util/test_lb_policies.cc +++ b/test/core/util/test_lb_policies.cc @@ -48,19 +48,25 @@ namespace { // A minimal forwarding class to avoid implementing a standalone test LB. class ForwardingLoadBalancingPolicy : public LoadBalancingPolicy { public: - ForwardingLoadBalancingPolicy( - UniquePtr delegating_helper, Args args, - const std::string& delegate_policy_name, intptr_t initial_refcount = 1) - : LoadBalancingPolicy(std::move(args), initial_refcount) { + ForwardingLoadBalancingPolicy(Args args, + const std::string& delegate_policy_name) + : LoadBalancingPolicy(std::move(args)) { Args delegate_args; delegate_args.combiner = combiner(); - delegate_args.channel_control_helper = std::move(delegating_helper); + delegate_args.client_channel_factory = client_channel_factory(); + delegate_args.subchannel_pool = subchannel_pool()->Ref(); delegate_args.args = args.args; delegate_args.lb_config = args.lb_config; delegate_ = LoadBalancingPolicyRegistry::CreateLoadBalancingPolicy( delegate_policy_name.c_str(), std::move(delegate_args)); grpc_pollset_set_add_pollset_set(delegate_->interested_parties(), interested_parties()); + // Give re-resolution closure to delegate. + GRPC_CLOSURE_INIT(&on_delegate_request_reresolution_, + OnDelegateRequestReresolutionLocked, this, + grpc_combiner_scheduler(combiner())); + Ref().release(); // held by callback. + delegate_->SetReresolutionClosureLocked(&on_delegate_request_reresolution_); } ~ForwardingLoadBalancingPolicy() override = default; @@ -70,6 +76,35 @@ class ForwardingLoadBalancingPolicy : public LoadBalancingPolicy { delegate_->UpdateLocked(args, lb_config); } + bool PickLocked(PickState* pick, grpc_error** error) override { + return delegate_->PickLocked(pick, error); + } + + void CancelPickLocked(PickState* pick, grpc_error* error) override { + delegate_->CancelPickLocked(pick, error); + } + + void CancelMatchingPicksLocked(uint32_t initial_metadata_flags_mask, + uint32_t initial_metadata_flags_eq, + grpc_error* error) override { + delegate_->CancelMatchingPicksLocked(initial_metadata_flags_mask, + initial_metadata_flags_eq, error); + } + + void NotifyOnStateChangeLocked(grpc_connectivity_state* state, + grpc_closure* closure) override { + delegate_->NotifyOnStateChangeLocked(state, closure); + } + + grpc_connectivity_state CheckConnectivityLocked( + grpc_error** connectivity_error) override { + return delegate_->CheckConnectivityLocked(connectivity_error); + } + + void HandOffPendingPicksLocked(LoadBalancingPolicy* new_policy) override { + delegate_->HandOffPendingPicksLocked(new_policy); + } + void ExitIdleLocked() override { delegate_->ExitIdleLocked(); } void ResetBackoffLocked() override { delegate_->ResetBackoffLocked(); } @@ -81,9 +116,26 @@ class ForwardingLoadBalancingPolicy : public LoadBalancingPolicy { } private: - void ShutdownLocked() override { delegate_.reset(); } + void ShutdownLocked() override { + delegate_.reset(); + TryReresolutionLocked(&grpc_trace_forwarding_lb, GRPC_ERROR_CANCELLED); + } + + static void OnDelegateRequestReresolutionLocked(void* arg, + grpc_error* error) { + ForwardingLoadBalancingPolicy* self = + static_cast(arg); + if (error != GRPC_ERROR_NONE || self->delegate_ == nullptr) { + self->Unref(); + return; + } + self->TryReresolutionLocked(&grpc_trace_forwarding_lb, GRPC_ERROR_NONE); + self->delegate_->SetReresolutionClosureLocked( + &self->on_delegate_request_reresolution_); + } OrphanablePtr delegate_; + grpc_closure on_delegate_request_reresolution_; }; // @@ -98,13 +150,10 @@ class InterceptRecvTrailingMetadataLoadBalancingPolicy public: InterceptRecvTrailingMetadataLoadBalancingPolicy( Args args, InterceptRecvTrailingMetadataCallback cb, void* user_data) - : ForwardingLoadBalancingPolicy( - UniquePtr(New( - RefCountedPtr( - this), - cb, user_data)), - std::move(args), /*delegate_lb_policy_name=*/"pick_first", - /*initial_refcount=*/2) {} + : ForwardingLoadBalancingPolicy(std::move(args), + /*delegate_lb_policy_name=*/"pick_first"), + cb_(cb), + user_data_(user_data) {} ~InterceptRecvTrailingMetadataLoadBalancingPolicy() override = default; @@ -112,65 +161,17 @@ class InterceptRecvTrailingMetadataLoadBalancingPolicy return kInterceptRecvTrailingMetadataLbPolicyName; } - private: - class Picker : public SubchannelPicker { - public: - explicit Picker(UniquePtr delegate_picker, - InterceptRecvTrailingMetadataCallback cb, void* user_data) - : delegate_picker_(std::move(delegate_picker)), - cb_(cb), - user_data_(user_data) {} - - PickResult Pick(PickState* pick, grpc_error** error) override { - PickResult result = delegate_picker_->Pick(pick, error); - if (result == PICK_COMPLETE && pick->connected_subchannel != nullptr) { - New(pick, cb_, user_data_); // deletes itself - } - return result; - } - - private: - UniquePtr delegate_picker_; - InterceptRecvTrailingMetadataCallback cb_; - void* user_data_; - }; - - class Helper : public ChannelControlHelper { - public: - Helper( - RefCountedPtr parent, - InterceptRecvTrailingMetadataCallback cb, void* user_data) - : parent_(std::move(parent)), cb_(cb), user_data_(user_data) {} - - Subchannel* CreateSubchannel(const grpc_channel_args& args) override { - return parent_->channel_control_helper()->CreateSubchannel(args); - } - - grpc_channel* CreateChannel(const char* target, - grpc_client_channel_type type, - const grpc_channel_args& args) override { - return parent_->channel_control_helper()->CreateChannel(target, type, - args); - } - - void UpdateState(grpc_connectivity_state state, grpc_error* state_error, - UniquePtr picker) override { - parent_->channel_control_helper()->UpdateState( - state, state_error, - UniquePtr( - New(std::move(picker), cb_, user_data_))); - } - - void RequestReresolution() override { - parent_->channel_control_helper()->RequestReresolution(); - } - - private: - RefCountedPtr parent_; - InterceptRecvTrailingMetadataCallback cb_; - void* user_data_; - }; + bool PickLocked(PickState* pick, grpc_error** error) override { + bool ret = ForwardingLoadBalancingPolicy::PickLocked(pick, error); + // Note: This assumes that the delegate policy does not + // intercepting recv_trailing_metadata. If we ever need to use + // this with a delegate policy that does, then we'll need to + // handle async pick returns separately. + New(pick, cb_, user_data_); // deletes itself + return ret; + } + private: class TrailingMetadataHandler { public: TrailingMetadataHandler(PickState* pick, @@ -203,6 +204,9 @@ class InterceptRecvTrailingMetadataLoadBalancingPolicy grpc_closure* original_recv_trailing_metadata_ready_ = nullptr; grpc_metadata_batch* recv_trailing_metadata_ = nullptr; }; + + InterceptRecvTrailingMetadataCallback cb_; + void* user_data_; }; class InterceptTrailingFactory : public LoadBalancingPolicyFactory { diff --git a/test/cpp/microbenchmarks/bm_call_create.cc b/test/cpp/microbenchmarks/bm_call_create.cc index e57650fe5b7..973f47beaf7 100644 --- a/test/cpp/microbenchmarks/bm_call_create.cc +++ b/test/cpp/microbenchmarks/bm_call_create.cc @@ -570,7 +570,6 @@ static void BM_IsolatedFilter(benchmark::State& state) { } gpr_arena_destroy(call_args.arena); grpc_channel_stack_destroy(channel_stack); - grpc_core::ExecCtx::Get()->Flush(); gpr_free(channel_stack); gpr_free(call_stack); diff --git a/tools/doxygen/Doxyfile.core.internal b/tools/doxygen/Doxyfile.core.internal index 3533c7c00c5..d1a2debd7e3 100644 --- a/tools/doxygen/Doxyfile.core.internal +++ b/tools/doxygen/Doxyfile.core.internal @@ -936,6 +936,8 @@ src/core/ext/filters/client_channel/proxy_mapper.cc \ src/core/ext/filters/client_channel/proxy_mapper.h \ src/core/ext/filters/client_channel/proxy_mapper_registry.cc \ src/core/ext/filters/client_channel/proxy_mapper_registry.h \ +src/core/ext/filters/client_channel/request_routing.cc \ +src/core/ext/filters/client_channel/request_routing.h \ src/core/ext/filters/client_channel/resolver.cc \ src/core/ext/filters/client_channel/resolver.h \ src/core/ext/filters/client_channel/resolver/README.md \ @@ -960,8 +962,6 @@ src/core/ext/filters/client_channel/resolver_registry.cc \ src/core/ext/filters/client_channel/resolver_registry.h \ src/core/ext/filters/client_channel/resolver_result_parsing.cc \ src/core/ext/filters/client_channel/resolver_result_parsing.h \ -src/core/ext/filters/client_channel/resolving_lb_policy.cc \ -src/core/ext/filters/client_channel/resolving_lb_policy.h \ src/core/ext/filters/client_channel/retry_throttle.cc \ src/core/ext/filters/client_channel/retry_throttle.h \ src/core/ext/filters/client_channel/server_address.cc \ diff --git a/tools/run_tests/generated/sources_and_headers.json b/tools/run_tests/generated/sources_and_headers.json index 823e17dd45a..84d5c45095f 100644 --- a/tools/run_tests/generated/sources_and_headers.json +++ b/tools/run_tests/generated/sources_and_headers.json @@ -9968,11 +9968,11 @@ "src/core/ext/filters/client_channel/parse_address.h", "src/core/ext/filters/client_channel/proxy_mapper.h", "src/core/ext/filters/client_channel/proxy_mapper_registry.h", + "src/core/ext/filters/client_channel/request_routing.h", "src/core/ext/filters/client_channel/resolver.h", "src/core/ext/filters/client_channel/resolver_factory.h", "src/core/ext/filters/client_channel/resolver_registry.h", "src/core/ext/filters/client_channel/resolver_result_parsing.h", - "src/core/ext/filters/client_channel/resolving_lb_policy.h", "src/core/ext/filters/client_channel/retry_throttle.h", "src/core/ext/filters/client_channel/server_address.h", "src/core/ext/filters/client_channel/subchannel.h", @@ -10015,6 +10015,8 @@ "src/core/ext/filters/client_channel/proxy_mapper.h", "src/core/ext/filters/client_channel/proxy_mapper_registry.cc", "src/core/ext/filters/client_channel/proxy_mapper_registry.h", + "src/core/ext/filters/client_channel/request_routing.cc", + "src/core/ext/filters/client_channel/request_routing.h", "src/core/ext/filters/client_channel/resolver.cc", "src/core/ext/filters/client_channel/resolver.h", "src/core/ext/filters/client_channel/resolver_factory.h", @@ -10022,8 +10024,6 @@ "src/core/ext/filters/client_channel/resolver_registry.h", "src/core/ext/filters/client_channel/resolver_result_parsing.cc", "src/core/ext/filters/client_channel/resolver_result_parsing.h", - "src/core/ext/filters/client_channel/resolving_lb_policy.cc", - "src/core/ext/filters/client_channel/resolving_lb_policy.h", "src/core/ext/filters/client_channel/retry_throttle.cc", "src/core/ext/filters/client_channel/retry_throttle.h", "src/core/ext/filters/client_channel/server_address.cc",