[call-v3] New client channel implementation (#36723)

Closes #36723

COPYBARA_INTEGRATE_REVIEW=https://github.com/grpc/grpc/pull/36723 from ctiller:transport-refs-7 e019b8f5ea
PiperOrigin-RevId: 638424601
pull/36759/head
Craig Tiller 6 months ago committed by Copybara-Service
parent ecee02a226
commit 34871fafa3
  1. 45
      BUILD
  2. 246
      CMakeLists.txt
  3. 3
      Makefile
  4. 7
      Package.swift
  5. 2
      bazel/experiments.bzl
  6. 88
      build_autogenerated.yaml
  7. 3
      config.m4
  8. 3
      config.w32
  9. 8
      gRPC-C++.podspec
  10. 11
      gRPC-Core.podspec
  11. 7
      grpc.gemspec
  12. 2669
      grpc.gyp
  13. 7
      package.xml
  14. 17
      src/core/BUILD
  15. 1392
      src/core/client_channel/client_channel.cc
  16. 245
      src/core/client_channel/client_channel.h
  17. 27
      src/core/client_channel/client_channel_filter.cc
  18. 13
      src/core/client_channel/client_channel_filter.h
  19. 8
      src/core/client_channel/client_channel_internal.h
  20. 335
      src/core/client_channel/load_balanced_call_destination.cc
  21. 49
      src/core/client_channel/load_balanced_call_destination.h
  22. 335
      src/core/client_channel/subchannel.cc
  23. 47
      src/core/client_channel/subchannel.h
  24. 4
      src/core/ext/filters/channel_idle/legacy_channel_idle_filter.cc
  25. 2
      src/core/ext/filters/channel_idle/legacy_channel_idle_filter.h
  26. 25
      src/core/lib/channel/channel_args.h
  27. 6
      src/core/lib/channel/channel_stack.h
  28. 25
      src/core/lib/channel/context.h
  29. 36
      src/core/lib/experiments/experiments.cc
  30. 16
      src/core/lib/experiments/experiments.h
  31. 2
      src/core/lib/experiments/experiments.yaml
  32. 6
      src/core/lib/promise/activity.h
  33. 5
      src/core/lib/promise/context.h
  34. 17
      src/core/lib/promise/map.h
  35. 1
      src/core/lib/promise/observable.h
  36. 6
      src/core/lib/promise/party.cc
  37. 5
      src/core/lib/promise/party.h
  38. 1
      src/core/lib/promise/promise.h
  39. 4
      src/core/lib/surface/call.h
  40. 17
      src/core/lib/surface/channel.cc
  41. 25
      src/core/lib/surface/channel.h
  42. 12
      src/core/lib/surface/channel_create.cc
  43. 5
      src/core/lib/surface/legacy_channel.cc
  44. 13
      src/core/lib/surface/legacy_channel.h
  45. 2
      src/core/lib/transport/call_destination.h
  46. 20
      src/core/lib/transport/call_filters.cc
  47. 7
      src/core/lib/transport/call_filters.h
  48. 40
      src/core/lib/transport/call_spine.h
  49. 11
      src/core/lib/transport/metadata_batch.h
  50. 10
      src/core/load_balancing/lb_policy.h
  51. 16
      src/core/telemetry/call_tracer.h
  52. 3
      src/python/grpcio/grpc_core_dependencies.py
  53. 11
      test/core/call/yodel/grpc_yodel_test.bzl
  54. 60
      test/core/call/yodel/yodel_test.cc
  55. 29
      test/core/call/yodel/yodel_test.h
  56. 35
      test/core/client_channel/BUILD
  57. 310
      test/core/client_channel/client_channel_test.cc
  58. 178
      test/core/client_channel/connected_subchannel_test.cc
  59. 1
      test/core/client_channel/corpus/client_channel/empty
  60. 1
      test/core/client_channel/corpus/connected_subchannel/empty
  61. 1
      test/core/client_channel/corpus/load_balanced_call_destination/empty
  62. 202
      test/core/client_channel/load_balanced_call_destination_test.cc
  63. 90
      test/core/client_channel/subchannel_args_test.cc
  64. 1
      test/core/promise/BUILD
  65. 18
      test/core/promise/map_test.cc
  66. 10
      test/core/promise/observable_test.cc
  67. 6
      test/core/surface/secure_channel_create_test.cc
  68. 10
      test/core/transport/test_suite/transport_test.cc
  69. 5
      test/core/transport/test_suite/transport_test.h
  70. 4
      tools/distrib/fix_build_deps.py
  71. 7
      tools/doxygen/Doxyfile.c++.internal
  72. 7
      tools/doxygen/Doxyfile.core.internal
  73. 72
      tools/run_tests/generated/tests.json

45
BUILD

@ -1797,16 +1797,17 @@ grpc_cc_library(
"gpr",
"grpc_public_hdrs",
"grpc_trace",
"orphanable",
"ref_counted_ptr",
"stats",
"//src/core:arena",
"//src/core:call_arena_allocator",
"//src/core:channel_args",
"//src/core:channel_stack_type",
"//src/core:compression",
"//src/core:connectivity_state",
"//src/core:iomgr_fwd",
"//src/core:ref_counted",
"//src/core:resource_quota",
"//src/core:slice",
"//src/core:stats_data",
"//src/core:time",
@ -1885,6 +1886,7 @@ grpc_cc_library(
"config",
"gpr",
"grpc_base",
"grpc_client_channel",
"grpc_public_hdrs",
"legacy_channel",
"ref_counted_ptr",
@ -1892,6 +1894,7 @@ grpc_cc_library(
"//src/core:arena",
"//src/core:channel_args",
"//src/core:channel_stack_type",
"//src/core:experiments",
"//src/core:iomgr_fwd",
"//src/core:ref_counted",
"//src/core:slice",
@ -3698,11 +3701,13 @@ grpc_cc_library(
grpc_cc_library(
name = "grpc_client_channel",
srcs = [
"//src/core:client_channel/client_channel.cc",
"//src/core:client_channel/client_channel_factory.cc",
"//src/core:client_channel/client_channel_filter.cc",
"//src/core:client_channel/client_channel_plugin.cc",
"//src/core:client_channel/dynamic_filters.cc",
"//src/core:client_channel/global_subchannel_pool.cc",
"//src/core:client_channel/load_balanced_call_destination.cc",
"//src/core:client_channel/local_subchannel_pool.cc",
"//src/core:client_channel/retry_filter.cc",
"//src/core:client_channel/retry_filter_legacy_call_data.cc",
@ -3710,10 +3715,12 @@ grpc_cc_library(
"//src/core:client_channel/subchannel_stream_client.cc",
],
hdrs = [
"//src/core:client_channel/client_channel.h",
"//src/core:client_channel/client_channel_factory.h",
"//src/core:client_channel/client_channel_filter.h",
"//src/core:client_channel/dynamic_filters.h",
"//src/core:client_channel/global_subchannel_pool.h",
"//src/core:client_channel/load_balanced_call_destination.h",
"//src/core:client_channel/local_subchannel_pool.h",
"//src/core:client_channel/retry_filter.h",
"//src/core:client_channel/retry_filter_legacy_call_data.h",
@ -3735,21 +3742,17 @@ grpc_cc_library(
"absl/strings:cord",
"absl/types:optional",
"absl/types:variant",
"@com_google_protobuf//upb:base",
"@com_google_protobuf//upb:mem",
"@com_google_protobuf//upb:message",
],
language = "c++",
visibility = ["@grpc:client_channel"],
deps = [
"api_trace",
"backoff",
"call_combiner",
"call_tracer",
"channel",
"channel_arg_names",
"channelz",
"config",
"config_vars",
"debug_location",
"endpoint_addresses",
"exec_ctx",
@ -3760,26 +3763,23 @@ grpc_cc_library(
"grpc_security_base",
"grpc_service_config_impl",
"grpc_trace",
"http_connect_handshaker",
"iomgr",
"iomgr_timer",
"lb_child_policy_handler",
"legacy_context",
"orphanable",
"parse_address",
"promise",
"protobuf_duration_upb",
"ref_counted_ptr",
"sockaddr_utils",
"stats",
"uri_parser",
"work_serializer",
"xds_orca_service_upb",
"xds_orca_upb",
"//src/core:activity",
"//src/core:arena",
"//src/core:arena_promise",
"//src/core:backend_metric_parser",
"//src/core:call_destination",
"//src/core:call_filters",
"//src/core:call_spine",
"//src/core:cancel_callback",
"//src/core:channel_args",
"//src/core:channel_fwd",
@ -3793,33 +3793,33 @@ grpc_cc_library(
"//src/core:connectivity_state",
"//src/core:construct_destruct",
"//src/core:context",
"//src/core:delegating_helper",
"//src/core:dual_ref_counted",
"//src/core:env",
"//src/core:error",
"//src/core:error_utils",
"//src/core:exec_ctx_wakeup_scheduler",
"//src/core:experiments",
"//src/core:gpr_atm",
"//src/core:gpr_manual_constructor",
"//src/core:grpc_backend_metric_data",
"//src/core:grpc_message_size_filter",
"//src/core:grpc_channel_idle_filter",
"//src/core:grpc_service_config",
"//src/core:idle_filter_state",
"//src/core:init_internally",
"//src/core:interception_chain",
"//src/core:iomgr_fwd",
"//src/core:json",
"//src/core:json_args",
"//src/core:json_channel_args",
"//src/core:json_object_loader",
"//src/core:latch",
"//src/core:lb_policy",
"//src/core:lb_policy_registry",
"//src/core:loop",
"//src/core:map",
"//src/core:memory_quota",
"//src/core:metadata",
"//src/core:metadata_batch",
"//src/core:metrics",
"//src/core:observable",
"//src/core:pipe",
"//src/core:poll",
"//src/core:pollset_set",
"//src/core:proxy_mapper",
"//src/core:proxy_mapper_registry",
"//src/core:ref_counted",
"//src/core:resolved_address",
@ -3827,11 +3827,13 @@ grpc_cc_library(
"//src/core:retry_service_config",
"//src/core:retry_throttle",
"//src/core:seq",
"//src/core:service_config_parser",
"//src/core:single_set_ptr",
"//src/core:sleep",
"//src/core:slice",
"//src/core:slice_buffer",
"//src/core:slice_refcount",
"//src/core:stats_data",
"//src/core:status_flag",
"//src/core:status_helper",
"//src/core:subchannel_connector",
"//src/core:subchannel_interface",
@ -3840,7 +3842,6 @@ grpc_cc_library(
"//src/core:try_seq",
"//src/core:unique_type_name",
"//src/core:useful",
"//src/core:validation_errors",
],
)

246
CMakeLists.txt generated

@ -1002,7 +1002,9 @@ if(gRPC_BUILD_TESTS)
add_dependencies(buildtests_cxx client_authority_filter_test)
add_dependencies(buildtests_cxx client_callback_end2end_test)
add_dependencies(buildtests_cxx client_channel_service_config_test)
add_dependencies(buildtests_cxx client_channel_test)
if(_gRPC_PLATFORM_LINUX OR _gRPC_PLATFORM_POSIX)
add_dependencies(buildtests_cxx client_channel_test)
endif()
add_dependencies(buildtests_cxx client_context_test_peer_test)
if(_gRPC_PLATFORM_LINUX OR _gRPC_PLATFORM_MAC OR _gRPC_PLATFORM_POSIX)
add_dependencies(buildtests_cxx client_fork_test)
@ -1026,6 +1028,9 @@ if(gRPC_BUILD_TESTS)
add_dependencies(buildtests_cxx compressed_payload_test)
add_dependencies(buildtests_cxx compression_test)
add_dependencies(buildtests_cxx concurrent_connectivity_test)
if(_gRPC_PLATFORM_LINUX OR _gRPC_PLATFORM_POSIX)
add_dependencies(buildtests_cxx connected_subchannel_test)
endif()
add_dependencies(buildtests_cxx connection_prefix_bad_client_test)
add_dependencies(buildtests_cxx connection_refused_test)
add_dependencies(buildtests_cxx connectivity_state_test)
@ -1194,6 +1199,9 @@ if(gRPC_BUILD_TESTS)
add_dependencies(buildtests_cxx latch_test)
add_dependencies(buildtests_cxx lb_get_cpu_stats_test)
add_dependencies(buildtests_cxx lb_load_data_store_test)
if(_gRPC_PLATFORM_LINUX OR _gRPC_PLATFORM_POSIX)
add_dependencies(buildtests_cxx load_balanced_call_destination_test)
endif()
add_dependencies(buildtests_cxx load_config_test)
add_dependencies(buildtests_cxx load_file_test)
if(_gRPC_PLATFORM_LINUX OR _gRPC_PLATFORM_POSIX)
@ -1436,6 +1444,7 @@ if(gRPC_BUILD_TESTS)
add_dependencies(buildtests_cxx streams_not_seen_test)
add_dependencies(buildtests_cxx string_ref_test)
add_dependencies(buildtests_cxx string_test)
add_dependencies(buildtests_cxx subchannel_args_test)
add_dependencies(buildtests_cxx switch_test)
add_dependencies(buildtests_cxx sync_test)
add_dependencies(buildtests_cxx system_roots_test)
@ -1830,6 +1839,7 @@ add_library(grpc
src/core/channelz/channelz.cc
src/core/channelz/channelz_registry.cc
src/core/client_channel/backup_poller.cc
src/core/client_channel/client_channel.cc
src/core/client_channel/client_channel_factory.cc
src/core/client_channel/client_channel_filter.cc
src/core/client_channel/client_channel_plugin.cc
@ -1837,6 +1847,7 @@ add_library(grpc
src/core/client_channel/config_selector.cc
src/core/client_channel/dynamic_filters.cc
src/core/client_channel/global_subchannel_pool.cc
src/core/client_channel/load_balanced_call_destination.cc
src/core/client_channel/local_subchannel_pool.cc
src/core/client_channel/retry_filter.cc
src/core/client_channel/retry_filter_legacy_call_data.cc
@ -2292,6 +2303,7 @@ add_library(grpc
src/core/lib/event_engine/work_queue/basic_work_queue.cc
src/core/lib/experiments/config.cc
src/core/lib/experiments/experiments.cc
src/core/lib/gprpp/dump_args.cc
src/core/lib/gprpp/load_file.cc
src/core/lib/gprpp/per_cpu.cc
src/core/lib/gprpp/posix/directory_reader.cc
@ -2927,6 +2939,7 @@ add_library(grpc_unsecure
src/core/channelz/channelz.cc
src/core/channelz/channelz_registry.cc
src/core/client_channel/backup_poller.cc
src/core/client_channel/client_channel.cc
src/core/client_channel/client_channel_factory.cc
src/core/client_channel/client_channel_filter.cc
src/core/client_channel/client_channel_plugin.cc
@ -2934,6 +2947,7 @@ add_library(grpc_unsecure
src/core/client_channel/config_selector.cc
src/core/client_channel/dynamic_filters.cc
src/core/client_channel/global_subchannel_pool.cc
src/core/client_channel/load_balanced_call_destination.cc
src/core/client_channel/local_subchannel_pool.cc
src/core/client_channel/retry_filter.cc
src/core/client_channel/retry_filter_legacy_call_data.cc
@ -3085,6 +3099,7 @@ add_library(grpc_unsecure
src/core/lib/event_engine/work_queue/basic_work_queue.cc
src/core/lib/experiments/config.cc
src/core/lib/experiments/experiments.cc
src/core/lib/gprpp/dump_args.cc
src/core/lib/gprpp/load_file.cc
src/core/lib/gprpp/per_cpu.cc
src/core/lib/gprpp/ref_counted_string.cc
@ -5203,6 +5218,7 @@ add_library(grpc_authorization_provider
src/core/lib/event_engine/work_queue/basic_work_queue.cc
src/core/lib/experiments/config.cc
src/core/lib/experiments/experiments.cc
src/core/lib/gprpp/dump_args.cc
src/core/lib/gprpp/load_file.cc
src/core/lib/gprpp/per_cpu.cc
src/core/lib/gprpp/ref_counted_string.cc
@ -8506,6 +8522,7 @@ add_executable(call_filters_test
src/core/lib/debug/trace.cc
src/core/lib/experiments/config.cc
src/core/lib/experiments/experiments.cc
src/core/lib/gprpp/dump_args.cc
src/core/lib/gprpp/ref_counted_string.cc
src/core/lib/gprpp/status_helper.cc
src/core/lib/gprpp/time.cc
@ -10526,46 +10543,56 @@ target_link_libraries(client_channel_service_config_test
endif()
if(gRPC_BUILD_TESTS)
if(_gRPC_PLATFORM_LINUX OR _gRPC_PLATFORM_POSIX)
add_executable(client_channel_test
test/core/client_channel/client_channel_test.cc
)
if(WIN32 AND MSVC)
if(BUILD_SHARED_LIBS)
target_compile_definitions(client_channel_test
PRIVATE
"GPR_DLL_IMPORTS"
"GRPC_DLL_IMPORTS"
)
add_executable(client_channel_test
${_gRPC_PROTO_GENS_DIR}/test/core/event_engine/fuzzing_event_engine/fuzzing_event_engine.pb.cc
${_gRPC_PROTO_GENS_DIR}/test/core/event_engine/fuzzing_event_engine/fuzzing_event_engine.grpc.pb.cc
${_gRPC_PROTO_GENS_DIR}/test/core/event_engine/fuzzing_event_engine/fuzzing_event_engine.pb.h
${_gRPC_PROTO_GENS_DIR}/test/core/event_engine/fuzzing_event_engine/fuzzing_event_engine.grpc.pb.h
test/core/call/yodel/test_main.cc
test/core/call/yodel/yodel_test.cc
test/core/client_channel/client_channel_test.cc
test/core/event_engine/fuzzing_event_engine/fuzzing_event_engine.cc
)
if(WIN32 AND MSVC)
if(BUILD_SHARED_LIBS)
target_compile_definitions(client_channel_test
PRIVATE
"GPR_DLL_IMPORTS"
"GRPC_DLL_IMPORTS"
)
endif()
endif()
endif()
target_compile_features(client_channel_test PUBLIC cxx_std_14)
target_include_directories(client_channel_test
PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}
${CMAKE_CURRENT_SOURCE_DIR}/include
${_gRPC_ADDRESS_SORTING_INCLUDE_DIR}
${_gRPC_RE2_INCLUDE_DIR}
${_gRPC_SSL_INCLUDE_DIR}
${_gRPC_UPB_GENERATED_DIR}
${_gRPC_UPB_GRPC_GENERATED_DIR}
${_gRPC_UPB_INCLUDE_DIR}
${_gRPC_XXHASH_INCLUDE_DIR}
${_gRPC_ZLIB_INCLUDE_DIR}
third_party/googletest/googletest/include
third_party/googletest/googletest
third_party/googletest/googlemock/include
third_party/googletest/googlemock
${_gRPC_PROTO_GENS_DIR}
)
target_compile_features(client_channel_test PUBLIC cxx_std_14)
target_include_directories(client_channel_test
PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}
${CMAKE_CURRENT_SOURCE_DIR}/include
${_gRPC_ADDRESS_SORTING_INCLUDE_DIR}
${_gRPC_RE2_INCLUDE_DIR}
${_gRPC_SSL_INCLUDE_DIR}
${_gRPC_UPB_GENERATED_DIR}
${_gRPC_UPB_GRPC_GENERATED_DIR}
${_gRPC_UPB_INCLUDE_DIR}
${_gRPC_XXHASH_INCLUDE_DIR}
${_gRPC_ZLIB_INCLUDE_DIR}
third_party/googletest/googletest/include
third_party/googletest/googletest
third_party/googletest/googlemock/include
third_party/googletest/googlemock
${_gRPC_PROTO_GENS_DIR}
)
target_link_libraries(client_channel_test
${_gRPC_ALLTARGETS_LIBRARIES}
gtest
grpc_test_util
)
target_link_libraries(client_channel_test
${_gRPC_ALLTARGETS_LIBRARIES}
gtest
${_gRPC_PROTOBUF_LIBRARIES}
grpc_test_util
)
endif()
endif()
if(gRPC_BUILD_TESTS)
@ -11340,6 +11367,58 @@ target_link_libraries(concurrent_connectivity_test
)
endif()
if(gRPC_BUILD_TESTS)
if(_gRPC_PLATFORM_LINUX OR _gRPC_PLATFORM_POSIX)
add_executable(connected_subchannel_test
${_gRPC_PROTO_GENS_DIR}/test/core/event_engine/fuzzing_event_engine/fuzzing_event_engine.pb.cc
${_gRPC_PROTO_GENS_DIR}/test/core/event_engine/fuzzing_event_engine/fuzzing_event_engine.grpc.pb.cc
${_gRPC_PROTO_GENS_DIR}/test/core/event_engine/fuzzing_event_engine/fuzzing_event_engine.pb.h
${_gRPC_PROTO_GENS_DIR}/test/core/event_engine/fuzzing_event_engine/fuzzing_event_engine.grpc.pb.h
test/core/call/yodel/test_main.cc
test/core/call/yodel/yodel_test.cc
test/core/client_channel/connected_subchannel_test.cc
test/core/event_engine/fuzzing_event_engine/fuzzing_event_engine.cc
)
if(WIN32 AND MSVC)
if(BUILD_SHARED_LIBS)
target_compile_definitions(connected_subchannel_test
PRIVATE
"GPR_DLL_IMPORTS"
"GRPC_DLL_IMPORTS"
)
endif()
endif()
target_compile_features(connected_subchannel_test PUBLIC cxx_std_14)
target_include_directories(connected_subchannel_test
PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}
${CMAKE_CURRENT_SOURCE_DIR}/include
${_gRPC_ADDRESS_SORTING_INCLUDE_DIR}
${_gRPC_RE2_INCLUDE_DIR}
${_gRPC_SSL_INCLUDE_DIR}
${_gRPC_UPB_GENERATED_DIR}
${_gRPC_UPB_GRPC_GENERATED_DIR}
${_gRPC_UPB_INCLUDE_DIR}
${_gRPC_XXHASH_INCLUDE_DIR}
${_gRPC_ZLIB_INCLUDE_DIR}
third_party/googletest/googletest/include
third_party/googletest/googletest
third_party/googletest/googlemock/include
third_party/googletest/googlemock
${_gRPC_PROTO_GENS_DIR}
)
target_link_libraries(connected_subchannel_test
${_gRPC_ALLTARGETS_LIBRARIES}
gtest
${_gRPC_PROTOBUF_LIBRARIES}
grpc_test_util
)
endif()
endif()
if(gRPC_BUILD_TESTS)
@ -17832,6 +17911,7 @@ add_executable(interception_chain_test
src/core/lib/event_engine/work_queue/basic_work_queue.cc
src/core/lib/experiments/config.cc
src/core/lib/experiments/experiments.cc
src/core/lib/gprpp/dump_args.cc
src/core/lib/gprpp/load_file.cc
src/core/lib/gprpp/per_cpu.cc
src/core/lib/gprpp/ref_counted_string.cc
@ -18985,6 +19065,58 @@ target_link_libraries(lb_load_data_store_test
)
endif()
if(gRPC_BUILD_TESTS)
if(_gRPC_PLATFORM_LINUX OR _gRPC_PLATFORM_POSIX)
add_executable(load_balanced_call_destination_test
${_gRPC_PROTO_GENS_DIR}/test/core/event_engine/fuzzing_event_engine/fuzzing_event_engine.pb.cc
${_gRPC_PROTO_GENS_DIR}/test/core/event_engine/fuzzing_event_engine/fuzzing_event_engine.grpc.pb.cc
${_gRPC_PROTO_GENS_DIR}/test/core/event_engine/fuzzing_event_engine/fuzzing_event_engine.pb.h
${_gRPC_PROTO_GENS_DIR}/test/core/event_engine/fuzzing_event_engine/fuzzing_event_engine.grpc.pb.h
test/core/call/yodel/test_main.cc
test/core/call/yodel/yodel_test.cc
test/core/client_channel/load_balanced_call_destination_test.cc
test/core/event_engine/fuzzing_event_engine/fuzzing_event_engine.cc
)
if(WIN32 AND MSVC)
if(BUILD_SHARED_LIBS)
target_compile_definitions(load_balanced_call_destination_test
PRIVATE
"GPR_DLL_IMPORTS"
"GRPC_DLL_IMPORTS"
)
endif()
endif()
target_compile_features(load_balanced_call_destination_test PUBLIC cxx_std_14)
target_include_directories(load_balanced_call_destination_test
PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}
${CMAKE_CURRENT_SOURCE_DIR}/include
${_gRPC_ADDRESS_SORTING_INCLUDE_DIR}
${_gRPC_RE2_INCLUDE_DIR}
${_gRPC_SSL_INCLUDE_DIR}
${_gRPC_UPB_GENERATED_DIR}
${_gRPC_UPB_GRPC_GENERATED_DIR}
${_gRPC_UPB_INCLUDE_DIR}
${_gRPC_XXHASH_INCLUDE_DIR}
${_gRPC_ZLIB_INCLUDE_DIR}
third_party/googletest/googletest/include
third_party/googletest/googletest
third_party/googletest/googlemock/include
third_party/googletest/googlemock
${_gRPC_PROTO_GENS_DIR}
)
target_link_libraries(load_balanced_call_destination_test
${_gRPC_ALLTARGETS_LIBRARIES}
gtest
${_gRPC_PROTOBUF_LIBRARIES}
grpc_test_util
)
endif()
endif()
if(gRPC_BUILD_TESTS)
@ -29324,6 +29456,48 @@ target_link_libraries(string_test
)
endif()
if(gRPC_BUILD_TESTS)
add_executable(subchannel_args_test
test/core/client_channel/subchannel_args_test.cc
)
if(WIN32 AND MSVC)
if(BUILD_SHARED_LIBS)
target_compile_definitions(subchannel_args_test
PRIVATE
"GPR_DLL_IMPORTS"
"GRPC_DLL_IMPORTS"
)
endif()
endif()
target_compile_features(subchannel_args_test PUBLIC cxx_std_14)
target_include_directories(subchannel_args_test
PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}
${CMAKE_CURRENT_SOURCE_DIR}/include
${_gRPC_ADDRESS_SORTING_INCLUDE_DIR}
${_gRPC_RE2_INCLUDE_DIR}
${_gRPC_SSL_INCLUDE_DIR}
${_gRPC_UPB_GENERATED_DIR}
${_gRPC_UPB_GRPC_GENERATED_DIR}
${_gRPC_UPB_INCLUDE_DIR}
${_gRPC_XXHASH_INCLUDE_DIR}
${_gRPC_ZLIB_INCLUDE_DIR}
third_party/googletest/googletest/include
third_party/googletest/googletest
third_party/googletest/googlemock/include
third_party/googletest/googlemock
${_gRPC_PROTO_GENS_DIR}
)
target_link_libraries(subchannel_args_test
${_gRPC_ALLTARGETS_LIBRARIES}
gtest
grpc_test_util
)
endif()
if(gRPC_BUILD_TESTS)

3
Makefile generated

@ -670,6 +670,7 @@ LIBGRPC_SRC = \
src/core/channelz/channelz.cc \
src/core/channelz/channelz_registry.cc \
src/core/client_channel/backup_poller.cc \
src/core/client_channel/client_channel.cc \
src/core/client_channel/client_channel_factory.cc \
src/core/client_channel/client_channel_filter.cc \
src/core/client_channel/client_channel_plugin.cc \
@ -677,6 +678,7 @@ LIBGRPC_SRC = \
src/core/client_channel/config_selector.cc \
src/core/client_channel/dynamic_filters.cc \
src/core/client_channel/global_subchannel_pool.cc \
src/core/client_channel/load_balanced_call_destination.cc \
src/core/client_channel/local_subchannel_pool.cc \
src/core/client_channel/retry_filter.cc \
src/core/client_channel/retry_filter_legacy_call_data.cc \
@ -1137,6 +1139,7 @@ LIBGRPC_SRC = \
src/core/lib/experiments/config.cc \
src/core/lib/experiments/experiments.cc \
src/core/lib/gprpp/crash.cc \
src/core/lib/gprpp/dump_args.cc \
src/core/lib/gprpp/examine_stack.cc \
src/core/lib/gprpp/fork.cc \
src/core/lib/gprpp/host_port.cc \

7
Package.swift generated

@ -126,6 +126,8 @@ let package = Package(
"src/core/channelz/channelz_registry.h",
"src/core/client_channel/backup_poller.cc",
"src/core/client_channel/backup_poller.h",
"src/core/client_channel/client_channel.cc",
"src/core/client_channel/client_channel.h",
"src/core/client_channel/client_channel_factory.cc",
"src/core/client_channel/client_channel_factory.h",
"src/core/client_channel/client_channel_filter.cc",
@ -141,6 +143,8 @@ let package = Package(
"src/core/client_channel/dynamic_filters.h",
"src/core/client_channel/global_subchannel_pool.cc",
"src/core/client_channel/global_subchannel_pool.h",
"src/core/client_channel/load_balanced_call_destination.cc",
"src/core/client_channel/load_balanced_call_destination.h",
"src/core/client_channel/local_subchannel_pool.cc",
"src/core/client_channel/local_subchannel_pool.h",
"src/core/client_channel/retry_filter.cc",
@ -1255,6 +1259,8 @@ let package = Package(
"src/core/lib/gprpp/directory_reader.h",
"src/core/lib/gprpp/down_cast.h",
"src/core/lib/gprpp/dual_ref_counted.h",
"src/core/lib/gprpp/dump_args.cc",
"src/core/lib/gprpp/dump_args.h",
"src/core/lib/gprpp/env.h",
"src/core/lib/gprpp/examine_stack.cc",
"src/core/lib/gprpp/examine_stack.h",
@ -1480,6 +1486,7 @@ let package = Package(
"src/core/lib/promise/latch.h",
"src/core/lib/promise/loop.h",
"src/core/lib/promise/map.h",
"src/core/lib/promise/observable.h",
"src/core/lib/promise/party.cc",
"src/core/lib/promise/party.h",
"src/core/lib/promise/pipe.h",

@ -18,7 +18,6 @@
EXPERIMENT_ENABLES = {
"call_status_override_on_cancellation": "call_status_override_on_cancellation",
"call_v3": "call_v3",
"canary_client_privacy": "canary_client_privacy",
"client_privacy": "client_privacy",
"event_engine_client": "event_engine_client",
@ -45,6 +44,7 @@ EXPERIMENT_ENABLES = {
"unconstrained_max_quota_buffer_size": "unconstrained_max_quota_buffer_size",
"work_serializer_clears_time_cache": "work_serializer_clears_time_cache",
"work_serializer_dispatch": "event_engine_client,work_serializer_dispatch",
"call_v3": "call_v3,event_engine_client,event_engine_listener,work_serializer_dispatch",
}
EXPERIMENT_POLLERS = [

@ -227,6 +227,7 @@ libs:
- src/core/channelz/channelz.h
- src/core/channelz/channelz_registry.h
- src/core/client_channel/backup_poller.h
- src/core/client_channel/client_channel.h
- src/core/client_channel/client_channel_factory.h
- src/core/client_channel/client_channel_filter.h
- src/core/client_channel/client_channel_internal.h
@ -235,6 +236,7 @@ libs:
- src/core/client_channel/connector.h
- src/core/client_channel/dynamic_filters.h
- src/core/client_channel/global_subchannel_pool.h
- src/core/client_channel/load_balanced_call_destination.h
- src/core/client_channel/local_subchannel_pool.h
- src/core/client_channel/retry_filter.h
- src/core/client_channel/retry_filter_legacy_call_data.h
@ -883,6 +885,7 @@ libs:
- src/core/lib/gprpp/directory_reader.h
- src/core/lib/gprpp/down_cast.h
- src/core/lib/gprpp/dual_ref_counted.h
- src/core/lib/gprpp/dump_args.h
- src/core/lib/gprpp/if_list.h
- src/core/lib/gprpp/load_file.h
- src/core/lib/gprpp/manual_constructor.h
@ -991,6 +994,7 @@ libs:
- src/core/lib/promise/latch.h
- src/core/lib/promise/loop.h
- src/core/lib/promise/map.h
- src/core/lib/promise/observable.h
- src/core/lib/promise/party.h
- src/core/lib/promise/pipe.h
- src/core/lib/promise/poll.h
@ -1251,6 +1255,7 @@ libs:
- src/core/channelz/channelz.cc
- src/core/channelz/channelz_registry.cc
- src/core/client_channel/backup_poller.cc
- src/core/client_channel/client_channel.cc
- src/core/client_channel/client_channel_factory.cc
- src/core/client_channel/client_channel_filter.cc
- src/core/client_channel/client_channel_plugin.cc
@ -1258,6 +1263,7 @@ libs:
- src/core/client_channel/config_selector.cc
- src/core/client_channel/dynamic_filters.cc
- src/core/client_channel/global_subchannel_pool.cc
- src/core/client_channel/load_balanced_call_destination.cc
- src/core/client_channel/local_subchannel_pool.cc
- src/core/client_channel/retry_filter.cc
- src/core/client_channel/retry_filter_legacy_call_data.cc
@ -1713,6 +1719,7 @@ libs:
- src/core/lib/event_engine/work_queue/basic_work_queue.cc
- src/core/lib/experiments/config.cc
- src/core/lib/experiments/experiments.cc
- src/core/lib/gprpp/dump_args.cc
- src/core/lib/gprpp/load_file.cc
- src/core/lib/gprpp/per_cpu.cc
- src/core/lib/gprpp/posix/directory_reader.cc
@ -2215,6 +2222,7 @@ libs:
- src/core/channelz/channelz.h
- src/core/channelz/channelz_registry.h
- src/core/client_channel/backup_poller.h
- src/core/client_channel/client_channel.h
- src/core/client_channel/client_channel_factory.h
- src/core/client_channel/client_channel_filter.h
- src/core/client_channel/client_channel_internal.h
@ -2223,6 +2231,7 @@ libs:
- src/core/client_channel/connector.h
- src/core/client_channel/dynamic_filters.h
- src/core/client_channel/global_subchannel_pool.h
- src/core/client_channel/load_balanced_call_destination.h
- src/core/client_channel/local_subchannel_pool.h
- src/core/client_channel/retry_filter.h
- src/core/client_channel/retry_filter_legacy_call_data.h
@ -2420,6 +2429,7 @@ libs:
- src/core/lib/gprpp/cpp_impl_of.h
- src/core/lib/gprpp/down_cast.h
- src/core/lib/gprpp/dual_ref_counted.h
- src/core/lib/gprpp/dump_args.h
- src/core/lib/gprpp/if_list.h
- src/core/lib/gprpp/load_file.h
- src/core/lib/gprpp/manual_constructor.h
@ -2526,6 +2536,7 @@ libs:
- src/core/lib/promise/latch.h
- src/core/lib/promise/loop.h
- src/core/lib/promise/map.h
- src/core/lib/promise/observable.h
- src/core/lib/promise/party.h
- src/core/lib/promise/pipe.h
- src/core/lib/promise/poll.h
@ -2708,6 +2719,7 @@ libs:
- src/core/channelz/channelz.cc
- src/core/channelz/channelz_registry.cc
- src/core/client_channel/backup_poller.cc
- src/core/client_channel/client_channel.cc
- src/core/client_channel/client_channel_factory.cc
- src/core/client_channel/client_channel_filter.cc
- src/core/client_channel/client_channel_plugin.cc
@ -2715,6 +2727,7 @@ libs:
- src/core/client_channel/config_selector.cc
- src/core/client_channel/dynamic_filters.cc
- src/core/client_channel/global_subchannel_pool.cc
- src/core/client_channel/load_balanced_call_destination.cc
- src/core/client_channel/local_subchannel_pool.cc
- src/core/client_channel/retry_filter.cc
- src/core/client_channel/retry_filter_legacy_call_data.cc
@ -2866,6 +2879,7 @@ libs:
- src/core/lib/event_engine/work_queue/basic_work_queue.cc
- src/core/lib/experiments/config.cc
- src/core/lib/experiments/experiments.cc
- src/core/lib/gprpp/dump_args.cc
- src/core/lib/gprpp/load_file.cc
- src/core/lib/gprpp/per_cpu.cc
- src/core/lib/gprpp/ref_counted_string.cc
@ -4510,6 +4524,7 @@ libs:
- src/core/lib/gprpp/cpp_impl_of.h
- src/core/lib/gprpp/down_cast.h
- src/core/lib/gprpp/dual_ref_counted.h
- src/core/lib/gprpp/dump_args.h
- src/core/lib/gprpp/if_list.h
- src/core/lib/gprpp/load_file.h
- src/core/lib/gprpp/manual_constructor.h
@ -4618,6 +4633,7 @@ libs:
- src/core/lib/promise/party.h
- src/core/lib/promise/pipe.h
- src/core/lib/promise/poll.h
- src/core/lib/promise/prioritized_race.h
- src/core/lib/promise/promise.h
- src/core/lib/promise/race.h
- src/core/lib/promise/seq.h
@ -4833,6 +4849,7 @@ libs:
- src/core/lib/event_engine/work_queue/basic_work_queue.cc
- src/core/lib/experiments/config.cc
- src/core/lib/experiments/experiments.cc
- src/core/lib/gprpp/dump_args.cc
- src/core/lib/gprpp/load_file.cc
- src/core/lib/gprpp/per_cpu.cc
- src/core/lib/gprpp/ref_counted_string.cc
@ -6378,6 +6395,7 @@ targets:
- src/core/lib/gprpp/chunked_vector.h
- src/core/lib/gprpp/down_cast.h
- src/core/lib/gprpp/dual_ref_counted.h
- src/core/lib/gprpp/dump_args.h
- src/core/lib/gprpp/if_list.h
- src/core/lib/gprpp/manual_constructor.h
- src/core/lib/gprpp/orphanable.h
@ -6468,6 +6486,7 @@ targets:
- src/core/lib/debug/trace.cc
- src/core/lib/experiments/config.cc
- src/core/lib/experiments/experiments.cc
- src/core/lib/gprpp/dump_args.cc
- src/core/lib/gprpp/ref_counted_string.cc
- src/core/lib/gprpp/status_helper.cc
- src/core/lib/gprpp/time.cc
@ -7869,12 +7888,22 @@ targets:
gtest: true
build: test
language: c++
headers: []
headers:
- test/core/call/yodel/yodel_test.h
- test/core/event_engine/fuzzing_event_engine/fuzzing_event_engine.h
src:
- test/core/event_engine/fuzzing_event_engine/fuzzing_event_engine.proto
- test/core/call/yodel/test_main.cc
- test/core/call/yodel/yodel_test.cc
- test/core/client_channel/client_channel_test.cc
- test/core/event_engine/fuzzing_event_engine/fuzzing_event_engine.cc
deps:
- gtest
- protobuf
- grpc_test_util
platforms:
- linux
- posix
uses_polling: false
- name: client_context_test_peer_test
gtest: true
@ -8225,6 +8254,27 @@ targets:
deps:
- gtest
- grpc_test_util
- name: connected_subchannel_test
gtest: true
build: test
language: c++
headers:
- test/core/call/yodel/yodel_test.h
- test/core/event_engine/fuzzing_event_engine/fuzzing_event_engine.h
src:
- test/core/event_engine/fuzzing_event_engine/fuzzing_event_engine.proto
- test/core/call/yodel/test_main.cc
- test/core/call/yodel/yodel_test.cc
- test/core/client_channel/connected_subchannel_test.cc
- test/core/event_engine/fuzzing_event_engine/fuzzing_event_engine.cc
deps:
- gtest
- protobuf
- grpc_test_util
platforms:
- linux
- posix
uses_polling: false
- name: connection_prefix_bad_client_test
gtest: true
build: test
@ -11669,6 +11719,7 @@ targets:
- src/core/lib/gprpp/cpp_impl_of.h
- src/core/lib/gprpp/down_cast.h
- src/core/lib/gprpp/dual_ref_counted.h
- src/core/lib/gprpp/dump_args.h
- src/core/lib/gprpp/if_list.h
- src/core/lib/gprpp/load_file.h
- src/core/lib/gprpp/manual_constructor.h
@ -11776,6 +11827,7 @@ targets:
- src/core/lib/promise/party.h
- src/core/lib/promise/pipe.h
- src/core/lib/promise/poll.h
- src/core/lib/promise/prioritized_race.h
- src/core/lib/promise/promise.h
- src/core/lib/promise/race.h
- src/core/lib/promise/seq.h
@ -11961,6 +12013,7 @@ targets:
- src/core/lib/event_engine/work_queue/basic_work_queue.cc
- src/core/lib/experiments/config.cc
- src/core/lib/experiments/experiments.cc
- src/core/lib/gprpp/dump_args.cc
- src/core/lib/gprpp/load_file.cc
- src/core/lib/gprpp/per_cpu.cc
- src/core/lib/gprpp/ref_counted_string.cc
@ -12733,6 +12786,27 @@ targets:
- gtest
- grpc++
- grpc_test_util
- name: load_balanced_call_destination_test
gtest: true
build: test
language: c++
headers:
- test/core/call/yodel/yodel_test.h
- test/core/event_engine/fuzzing_event_engine/fuzzing_event_engine.h
src:
- test/core/event_engine/fuzzing_event_engine/fuzzing_event_engine.proto
- test/core/call/yodel/test_main.cc
- test/core/call/yodel/yodel_test.cc
- test/core/client_channel/load_balanced_call_destination_test.cc
- test/core/event_engine/fuzzing_event_engine/fuzzing_event_engine.cc
deps:
- gtest
- protobuf
- grpc_test_util
platforms:
- linux
- posix
uses_polling: false
- name: load_config_test
gtest: true
build: test
@ -14639,6 +14713,7 @@ targets:
- src/core/lib/promise/map.h
- src/core/lib/promise/poll.h
- src/core/lib/promise/promise.h
- test/core/promise/poll_matcher.h
src:
- test/core/promise/map_test.cc
deps:
@ -18993,6 +19068,17 @@ targets:
- gtest
- grpc_test_util
uses_polling: false
- name: subchannel_args_test
gtest: true
build: test
language: c++
headers: []
src:
- test/core/client_channel/subchannel_args_test.cc
deps:
- gtest
- grpc_test_util
uses_polling: false
- name: switch_test
gtest: true
build: test

3
config.m4 generated

@ -45,6 +45,7 @@ if test "$PHP_GRPC" != "no"; then
src/core/channelz/channelz.cc \
src/core/channelz/channelz_registry.cc \
src/core/client_channel/backup_poller.cc \
src/core/client_channel/client_channel.cc \
src/core/client_channel/client_channel_factory.cc \
src/core/client_channel/client_channel_filter.cc \
src/core/client_channel/client_channel_plugin.cc \
@ -52,6 +53,7 @@ if test "$PHP_GRPC" != "no"; then
src/core/client_channel/config_selector.cc \
src/core/client_channel/dynamic_filters.cc \
src/core/client_channel/global_subchannel_pool.cc \
src/core/client_channel/load_balanced_call_destination.cc \
src/core/client_channel/local_subchannel_pool.cc \
src/core/client_channel/retry_filter.cc \
src/core/client_channel/retry_filter_legacy_call_data.cc \
@ -512,6 +514,7 @@ if test "$PHP_GRPC" != "no"; then
src/core/lib/experiments/config.cc \
src/core/lib/experiments/experiments.cc \
src/core/lib/gprpp/crash.cc \
src/core/lib/gprpp/dump_args.cc \
src/core/lib/gprpp/examine_stack.cc \
src/core/lib/gprpp/fork.cc \
src/core/lib/gprpp/host_port.cc \

3
config.w32 generated

@ -10,6 +10,7 @@ if (PHP_GRPC != "no") {
"src\\core\\channelz\\channelz.cc " +
"src\\core\\channelz\\channelz_registry.cc " +
"src\\core\\client_channel\\backup_poller.cc " +
"src\\core\\client_channel\\client_channel.cc " +
"src\\core\\client_channel\\client_channel_factory.cc " +
"src\\core\\client_channel\\client_channel_filter.cc " +
"src\\core\\client_channel\\client_channel_plugin.cc " +
@ -17,6 +18,7 @@ if (PHP_GRPC != "no") {
"src\\core\\client_channel\\config_selector.cc " +
"src\\core\\client_channel\\dynamic_filters.cc " +
"src\\core\\client_channel\\global_subchannel_pool.cc " +
"src\\core\\client_channel\\load_balanced_call_destination.cc " +
"src\\core\\client_channel\\local_subchannel_pool.cc " +
"src\\core\\client_channel\\retry_filter.cc " +
"src\\core\\client_channel\\retry_filter_legacy_call_data.cc " +
@ -477,6 +479,7 @@ if (PHP_GRPC != "no") {
"src\\core\\lib\\experiments\\config.cc " +
"src\\core\\lib\\experiments\\experiments.cc " +
"src\\core\\lib\\gprpp\\crash.cc " +
"src\\core\\lib\\gprpp\\dump_args.cc " +
"src\\core\\lib\\gprpp\\examine_stack.cc " +
"src\\core\\lib\\gprpp\\fork.cc " +
"src\\core\\lib\\gprpp\\host_port.cc " +

8
gRPC-C++.podspec generated

@ -268,6 +268,7 @@ Pod::Spec.new do |s|
'src/core/channelz/channelz.h',
'src/core/channelz/channelz_registry.h',
'src/core/client_channel/backup_poller.h',
'src/core/client_channel/client_channel.h',
'src/core/client_channel/client_channel_factory.h',
'src/core/client_channel/client_channel_filter.h',
'src/core/client_channel/client_channel_internal.h',
@ -276,6 +277,7 @@ Pod::Spec.new do |s|
'src/core/client_channel/connector.h',
'src/core/client_channel/dynamic_filters.h',
'src/core/client_channel/global_subchannel_pool.h',
'src/core/client_channel/load_balanced_call_destination.h',
'src/core/client_channel/local_subchannel_pool.h',
'src/core/client_channel/retry_filter.h',
'src/core/client_channel/retry_filter_legacy_call_data.h',
@ -968,6 +970,7 @@ Pod::Spec.new do |s|
'src/core/lib/gprpp/directory_reader.h',
'src/core/lib/gprpp/down_cast.h',
'src/core/lib/gprpp/dual_ref_counted.h',
'src/core/lib/gprpp/dump_args.h',
'src/core/lib/gprpp/env.h',
'src/core/lib/gprpp/examine_stack.h',
'src/core/lib/gprpp/fork.h',
@ -1089,6 +1092,7 @@ Pod::Spec.new do |s|
'src/core/lib/promise/latch.h',
'src/core/lib/promise/loop.h',
'src/core/lib/promise/map.h',
'src/core/lib/promise/observable.h',
'src/core/lib/promise/party.h',
'src/core/lib/promise/pipe.h',
'src/core/lib/promise/poll.h',
@ -1557,6 +1561,7 @@ Pod::Spec.new do |s|
'src/core/channelz/channelz.h',
'src/core/channelz/channelz_registry.h',
'src/core/client_channel/backup_poller.h',
'src/core/client_channel/client_channel.h',
'src/core/client_channel/client_channel_factory.h',
'src/core/client_channel/client_channel_filter.h',
'src/core/client_channel/client_channel_internal.h',
@ -1565,6 +1570,7 @@ Pod::Spec.new do |s|
'src/core/client_channel/connector.h',
'src/core/client_channel/dynamic_filters.h',
'src/core/client_channel/global_subchannel_pool.h',
'src/core/client_channel/load_balanced_call_destination.h',
'src/core/client_channel/local_subchannel_pool.h',
'src/core/client_channel/retry_filter.h',
'src/core/client_channel/retry_filter_legacy_call_data.h',
@ -2239,6 +2245,7 @@ Pod::Spec.new do |s|
'src/core/lib/gprpp/directory_reader.h',
'src/core/lib/gprpp/down_cast.h',
'src/core/lib/gprpp/dual_ref_counted.h',
'src/core/lib/gprpp/dump_args.h',
'src/core/lib/gprpp/env.h',
'src/core/lib/gprpp/examine_stack.h',
'src/core/lib/gprpp/fork.h',
@ -2360,6 +2367,7 @@ Pod::Spec.new do |s|
'src/core/lib/promise/latch.h',
'src/core/lib/promise/loop.h',
'src/core/lib/promise/map.h',
'src/core/lib/promise/observable.h',
'src/core/lib/promise/party.h',
'src/core/lib/promise/pipe.h',
'src/core/lib/promise/poll.h',

11
gRPC-Core.podspec generated

@ -245,6 +245,8 @@ Pod::Spec.new do |s|
'src/core/channelz/channelz_registry.h',
'src/core/client_channel/backup_poller.cc',
'src/core/client_channel/backup_poller.h',
'src/core/client_channel/client_channel.cc',
'src/core/client_channel/client_channel.h',
'src/core/client_channel/client_channel_factory.cc',
'src/core/client_channel/client_channel_factory.h',
'src/core/client_channel/client_channel_filter.cc',
@ -260,6 +262,8 @@ Pod::Spec.new do |s|
'src/core/client_channel/dynamic_filters.h',
'src/core/client_channel/global_subchannel_pool.cc',
'src/core/client_channel/global_subchannel_pool.h',
'src/core/client_channel/load_balanced_call_destination.cc',
'src/core/client_channel/load_balanced_call_destination.h',
'src/core/client_channel/local_subchannel_pool.cc',
'src/core/client_channel/local_subchannel_pool.h',
'src/core/client_channel/retry_filter.cc',
@ -1374,6 +1378,8 @@ Pod::Spec.new do |s|
'src/core/lib/gprpp/directory_reader.h',
'src/core/lib/gprpp/down_cast.h',
'src/core/lib/gprpp/dual_ref_counted.h',
'src/core/lib/gprpp/dump_args.cc',
'src/core/lib/gprpp/dump_args.h',
'src/core/lib/gprpp/env.h',
'src/core/lib/gprpp/examine_stack.cc',
'src/core/lib/gprpp/examine_stack.h',
@ -1599,6 +1605,7 @@ Pod::Spec.new do |s|
'src/core/lib/promise/latch.h',
'src/core/lib/promise/loop.h',
'src/core/lib/promise/map.h',
'src/core/lib/promise/observable.h',
'src/core/lib/promise/party.cc',
'src/core/lib/promise/party.h',
'src/core/lib/promise/pipe.h',
@ -2357,6 +2364,7 @@ Pod::Spec.new do |s|
'src/core/channelz/channelz.h',
'src/core/channelz/channelz_registry.h',
'src/core/client_channel/backup_poller.h',
'src/core/client_channel/client_channel.h',
'src/core/client_channel/client_channel_factory.h',
'src/core/client_channel/client_channel_filter.h',
'src/core/client_channel/client_channel_internal.h',
@ -2365,6 +2373,7 @@ Pod::Spec.new do |s|
'src/core/client_channel/connector.h',
'src/core/client_channel/dynamic_filters.h',
'src/core/client_channel/global_subchannel_pool.h',
'src/core/client_channel/load_balanced_call_destination.h',
'src/core/client_channel/local_subchannel_pool.h',
'src/core/client_channel/retry_filter.h',
'src/core/client_channel/retry_filter_legacy_call_data.h',
@ -3019,6 +3028,7 @@ Pod::Spec.new do |s|
'src/core/lib/gprpp/directory_reader.h',
'src/core/lib/gprpp/down_cast.h',
'src/core/lib/gprpp/dual_ref_counted.h',
'src/core/lib/gprpp/dump_args.h',
'src/core/lib/gprpp/env.h',
'src/core/lib/gprpp/examine_stack.h',
'src/core/lib/gprpp/fork.h',
@ -3140,6 +3150,7 @@ Pod::Spec.new do |s|
'src/core/lib/promise/latch.h',
'src/core/lib/promise/loop.h',
'src/core/lib/promise/map.h',
'src/core/lib/promise/observable.h',
'src/core/lib/promise/party.h',
'src/core/lib/promise/pipe.h',
'src/core/lib/promise/poll.h',

7
grpc.gemspec generated

@ -132,6 +132,8 @@ Gem::Specification.new do |s|
s.files += %w( src/core/channelz/channelz_registry.h )
s.files += %w( src/core/client_channel/backup_poller.cc )
s.files += %w( src/core/client_channel/backup_poller.h )
s.files += %w( src/core/client_channel/client_channel.cc )
s.files += %w( src/core/client_channel/client_channel.h )
s.files += %w( src/core/client_channel/client_channel_factory.cc )
s.files += %w( src/core/client_channel/client_channel_factory.h )
s.files += %w( src/core/client_channel/client_channel_filter.cc )
@ -147,6 +149,8 @@ Gem::Specification.new do |s|
s.files += %w( src/core/client_channel/dynamic_filters.h )
s.files += %w( src/core/client_channel/global_subchannel_pool.cc )
s.files += %w( src/core/client_channel/global_subchannel_pool.h )
s.files += %w( src/core/client_channel/load_balanced_call_destination.cc )
s.files += %w( src/core/client_channel/load_balanced_call_destination.h )
s.files += %w( src/core/client_channel/local_subchannel_pool.cc )
s.files += %w( src/core/client_channel/local_subchannel_pool.h )
s.files += %w( src/core/client_channel/retry_filter.cc )
@ -1261,6 +1265,8 @@ Gem::Specification.new do |s|
s.files += %w( src/core/lib/gprpp/directory_reader.h )
s.files += %w( src/core/lib/gprpp/down_cast.h )
s.files += %w( src/core/lib/gprpp/dual_ref_counted.h )
s.files += %w( src/core/lib/gprpp/dump_args.cc )
s.files += %w( src/core/lib/gprpp/dump_args.h )
s.files += %w( src/core/lib/gprpp/env.h )
s.files += %w( src/core/lib/gprpp/examine_stack.cc )
s.files += %w( src/core/lib/gprpp/examine_stack.h )
@ -1486,6 +1492,7 @@ Gem::Specification.new do |s|
s.files += %w( src/core/lib/promise/latch.h )
s.files += %w( src/core/lib/promise/loop.h )
s.files += %w( src/core/lib/promise/map.h )
s.files += %w( src/core/lib/promise/observable.h )
s.files += %w( src/core/lib/promise/party.cc )
s.files += %w( src/core/lib/promise/party.h )
s.files += %w( src/core/lib/promise/pipe.h )

2669
grpc.gyp

File diff suppressed because it is too large Load Diff

7
package.xml generated

@ -114,6 +114,8 @@
<file baseinstalldir="/" name="src/core/channelz/channelz_registry.h" role="src" />
<file baseinstalldir="/" name="src/core/client_channel/backup_poller.cc" role="src" />
<file baseinstalldir="/" name="src/core/client_channel/backup_poller.h" role="src" />
<file baseinstalldir="/" name="src/core/client_channel/client_channel.cc" role="src" />
<file baseinstalldir="/" name="src/core/client_channel/client_channel.h" role="src" />
<file baseinstalldir="/" name="src/core/client_channel/client_channel_factory.cc" role="src" />
<file baseinstalldir="/" name="src/core/client_channel/client_channel_factory.h" role="src" />
<file baseinstalldir="/" name="src/core/client_channel/client_channel_filter.cc" role="src" />
@ -129,6 +131,8 @@
<file baseinstalldir="/" name="src/core/client_channel/dynamic_filters.h" role="src" />
<file baseinstalldir="/" name="src/core/client_channel/global_subchannel_pool.cc" role="src" />
<file baseinstalldir="/" name="src/core/client_channel/global_subchannel_pool.h" role="src" />
<file baseinstalldir="/" name="src/core/client_channel/load_balanced_call_destination.cc" role="src" />
<file baseinstalldir="/" name="src/core/client_channel/load_balanced_call_destination.h" role="src" />
<file baseinstalldir="/" name="src/core/client_channel/local_subchannel_pool.cc" role="src" />
<file baseinstalldir="/" name="src/core/client_channel/local_subchannel_pool.h" role="src" />
<file baseinstalldir="/" name="src/core/client_channel/retry_filter.cc" role="src" />
@ -1243,6 +1247,8 @@
<file baseinstalldir="/" name="src/core/lib/gprpp/directory_reader.h" role="src" />
<file baseinstalldir="/" name="src/core/lib/gprpp/down_cast.h" role="src" />
<file baseinstalldir="/" name="src/core/lib/gprpp/dual_ref_counted.h" role="src" />
<file baseinstalldir="/" name="src/core/lib/gprpp/dump_args.cc" role="src" />
<file baseinstalldir="/" name="src/core/lib/gprpp/dump_args.h" role="src" />
<file baseinstalldir="/" name="src/core/lib/gprpp/env.h" role="src" />
<file baseinstalldir="/" name="src/core/lib/gprpp/examine_stack.cc" role="src" />
<file baseinstalldir="/" name="src/core/lib/gprpp/examine_stack.h" role="src" />
@ -1468,6 +1474,7 @@
<file baseinstalldir="/" name="src/core/lib/promise/latch.h" role="src" />
<file baseinstalldir="/" name="src/core/lib/promise/loop.h" role="src" />
<file baseinstalldir="/" name="src/core/lib/promise/map.h" role="src" />
<file baseinstalldir="/" name="src/core/lib/promise/observable.h" role="src" />
<file baseinstalldir="/" name="src/core/lib/promise/party.cc" role="src" />
<file baseinstalldir="/" name="src/core/lib/promise/party.h" role="src" />
<file baseinstalldir="/" name="src/core/lib/promise/pipe.h" role="src" />

@ -3281,12 +3281,12 @@ grpc_cc_library(
language = "c++",
deps = [
"arena",
"call_destination",
"grpc_service_config",
"lb_policy",
"unique_type_name",
"//:call_tracer",
"//:gpr_public_hdrs",
"//:grpc_base",
"//:gpr",
"//:legacy_context",
],
)
@ -7509,12 +7509,10 @@ grpc_cc_library(
hdrs = [
"lib/transport/call_filters.h",
],
external_deps = [
"absl/log:check",
"absl/log:log",
],
external_deps = ["absl/log:check"],
deps = [
"call_final_info",
"dump_args",
"latch",
"map",
"message",
@ -7620,20 +7618,19 @@ grpc_cc_library(
hdrs = [
"lib/transport/call_spine.h",
],
external_deps = [
"absl/log:check",
"absl/log:log",
],
external_deps = ["absl/log:check"],
deps = [
"1999",
"call_arena_allocator",
"call_filters",
"dual_ref_counted",
"for_each",
"if",
"latch",
"message",
"metadata",
"pipe",
"prioritized_race",
"promise_status",
"status_flag",
"try_seq",

File diff suppressed because it is too large Load Diff

@ -0,0 +1,245 @@
//
// 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.
//
#ifndef GRPC_SRC_CORE_CLIENT_CHANNEL_CLIENT_CHANNEL_H
#define GRPC_SRC_CORE_CLIENT_CHANNEL_CLIENT_CHANNEL_H
#include <grpc/support/port_platform.h>
#include "absl/status/status.h"
#include "absl/status/statusor.h"
#include "absl/strings/string_view.h"
#include "src/core/client_channel/client_channel_factory.h"
#include "src/core/client_channel/config_selector.h"
#include "src/core/client_channel/subchannel.h"
#include "src/core/ext/filters/channel_idle/idle_filter_state.h"
#include "src/core/lib/gprpp/single_set_ptr.h"
#include "src/core/lib/promise/observable.h"
#include "src/core/lib/surface/channel.h"
#include "src/core/lib/transport/metadata.h"
#include "src/core/load_balancing/lb_policy.h"
#include "src/core/resolver/resolver.h"
#include "src/core/service_config/service_config.h"
namespace grpc_core {
class ClientChannel : public Channel {
public:
using PickerObservable =
Observable<RefCountedPtr<LoadBalancingPolicy::SubchannelPicker>>;
class CallDestinationFactory {
public:
struct RawPointerChannelArgTag {};
static absl::string_view ChannelArgName() {
return "grpc.internal.client_channel_call_destination";
}
virtual RefCountedPtr<UnstartedCallDestination> CreateCallDestination(
PickerObservable) = 0;
protected:
~CallDestinationFactory() = default;
};
static absl::StatusOr<OrphanablePtr<Channel>> Create(
std::string target, ChannelArgs channel_args);
// Do not instantiate directly -- use Create() instead.
ClientChannel(std::string target_uri, ChannelArgs args,
std::string uri_to_resolve,
RefCountedPtr<ServiceConfig> default_service_config,
ClientChannelFactory* client_channel_factory,
CallDestinationFactory* call_destination_factory);
~ClientChannel() override;
void Orphan() override;
grpc_call* CreateCall(grpc_call* parent_call, uint32_t propagation_mask,
grpc_completion_queue* cq,
grpc_pollset_set* /*pollset_set_alternative*/,
Slice path, absl::optional<Slice> authority,
Timestamp deadline, bool registered_method) override;
CallInitiator CreateCall(ClientMetadataHandle client_initial_metadata);
grpc_event_engine::experimental::EventEngine* event_engine() const override {
return event_engine_.get();
}
// TODO(ctiller): lame channels
bool IsLame() const override { return false; }
bool SupportsConnectivityWatcher() const override { return true; }
// Returns the current connectivity state. If try_to_connect is true,
// triggers a connection attempt if not already connected.
grpc_connectivity_state CheckConnectivityState(bool try_to_connect) override;
void WatchConnectivityState(grpc_connectivity_state last_observed_state,
Timestamp deadline, grpc_completion_queue* cq,
void* tag) override;
// Starts and stops a connectivity watch. The watcher will be initially
// notified as soon as the state changes from initial_state and then on
// every subsequent state change until either the watch is stopped or
// it is notified that the state has changed to SHUTDOWN.
//
// This is intended to be used when starting watches from code inside of
// C-core (e.g., for a nested control plane channel for things like xds).
void AddConnectivityWatcher(
grpc_connectivity_state initial_state,
OrphanablePtr<AsyncConnectivityStateWatcherInterface> watcher) override;
void RemoveConnectivityWatcher(
AsyncConnectivityStateWatcherInterface* watcher) override;
void GetInfo(const grpc_channel_info* channel_info) override;
void ResetConnectionBackoff() override;
void Ping(grpc_completion_queue* cq, void* tag) override;
// Flag that this object gets stored in channel args as a raw pointer.
struct RawPointerChannelArgTag {};
static absl::string_view ChannelArgName() {
return "grpc.internal.client_channel";
}
private:
class ClientChannelControlHelper;
class ResolverResultHandler;
class SubchannelWrapper;
void CreateResolverLocked() ABSL_EXCLUSIVE_LOCKS_REQUIRED(*work_serializer_);
void DestroyResolverAndLbPolicyLocked()
ABSL_EXCLUSIVE_LOCKS_REQUIRED(*work_serializer_);
void TryToConnectLocked() ABSL_EXCLUSIVE_LOCKS_REQUIRED(*work_serializer_);
void OnResolverResultChangedLocked(Resolver::Result result)
ABSL_EXCLUSIVE_LOCKS_REQUIRED(*work_serializer_);
void OnResolverErrorLocked(absl::Status status)
ABSL_EXCLUSIVE_LOCKS_REQUIRED(*work_serializer_);
absl::Status CreateOrUpdateLbPolicyLocked(
RefCountedPtr<LoadBalancingPolicy::Config> lb_policy_config,
const absl::optional<std::string>& health_check_service_name,
Resolver::Result result) ABSL_EXCLUSIVE_LOCKS_REQUIRED(*work_serializer_);
OrphanablePtr<LoadBalancingPolicy> CreateLbPolicyLocked(
const ChannelArgs& args) ABSL_EXCLUSIVE_LOCKS_REQUIRED(*work_serializer_);
void UpdateServiceConfigInControlPlaneLocked(
RefCountedPtr<ServiceConfig> service_config,
RefCountedPtr<ConfigSelector> config_selector, std::string lb_policy_name)
ABSL_EXCLUSIVE_LOCKS_REQUIRED(*work_serializer_);
void UpdateServiceConfigInDataPlaneLocked()
ABSL_EXCLUSIVE_LOCKS_REQUIRED(*work_serializer_);
void UpdateStateLocked(grpc_connectivity_state state,
const absl::Status& status, const char* reason)
ABSL_EXCLUSIVE_LOCKS_REQUIRED(*work_serializer_);
void UpdateStateAndPickerLocked(
grpc_connectivity_state state, const absl::Status& status,
const char* reason,
RefCountedPtr<LoadBalancingPolicy::SubchannelPicker> picker)
ABSL_EXCLUSIVE_LOCKS_REQUIRED(*work_serializer_);
void StartIdleTimer();
// Applies service config settings from config_selector to the call.
// May modify call context and client_initial_metadata.
absl::Status ApplyServiceConfigToCall(
ConfigSelector& config_selector,
ClientMetadata& client_initial_metadata) const;
const ChannelArgs channel_args_;
const std::shared_ptr<grpc_event_engine::experimental::EventEngine>
event_engine_;
const std::string uri_to_resolve_;
const size_t service_config_parser_index_;
const RefCountedPtr<ServiceConfig> default_service_config_;
ClientChannelFactory* const client_channel_factory_;
const std::string default_authority_;
channelz::ChannelNode* const channelz_node_;
// TODO(ctiller): unify with Channel
const RefCountedPtr<CallArenaAllocator> call_arena_allocator_;
GlobalStatsPluginRegistry::StatsPluginGroup stats_plugin_group_;
//
// Idleness state.
//
const Duration idle_timeout_;
IdleFilterState idle_state_{false};
SingleSetPtr<Activity, typename ActivityPtr::deleter_type> idle_activity_;
//
// Fields related to name resolution.
//
struct ResolverDataForCalls {
RefCountedPtr<ConfigSelector> config_selector;
RefCountedPtr<UnstartedCallDestination> call_destination;
};
Observable<absl::StatusOr<ResolverDataForCalls>> resolver_data_for_calls_;
//
// Fields related to LB picks.
//
PickerObservable picker_;
const RefCountedPtr<UnstartedCallDestination> call_destination_;
//
// Fields used in the control plane. Guarded by work_serializer.
//
std::shared_ptr<WorkSerializer> work_serializer_;
ConnectivityStateTracker state_tracker_ ABSL_GUARDED_BY(*work_serializer_);
OrphanablePtr<Resolver> resolver_ ABSL_GUARDED_BY(*work_serializer_);
bool previous_resolution_contained_addresses_
ABSL_GUARDED_BY(*work_serializer_) = false;
RefCountedPtr<ServiceConfig> saved_service_config_
ABSL_GUARDED_BY(*work_serializer_);
RefCountedPtr<ConfigSelector> saved_config_selector_
ABSL_GUARDED_BY(*work_serializer_);
OrphanablePtr<LoadBalancingPolicy> lb_policy_
ABSL_GUARDED_BY(*work_serializer_);
RefCountedPtr<SubchannelPoolInterface> subchannel_pool_
ABSL_GUARDED_BY(*work_serializer_);
// The number of SubchannelWrapper instances referencing a given Subchannel.
std::map<Subchannel*, int> subchannel_refcount_map_
ABSL_GUARDED_BY(*work_serializer_);
// The set of SubchannelWrappers that currently exist.
// No need to hold a ref, since the set is updated in the control-plane
// work_serializer when the SubchannelWrappers are created and destroyed.
absl::flat_hash_set<SubchannelWrapper*> subchannel_wrappers_
ABSL_GUARDED_BY(*work_serializer_);
int keepalive_time_ ABSL_GUARDED_BY(*work_serializer_) = -1;
absl::Status disconnect_error_ ABSL_GUARDED_BY(*work_serializer_);
//
// Fields accessed via GetChannelInfo().
//
Mutex info_mu_;
std::string info_lb_policy_name_ ABSL_GUARDED_BY(info_mu_);
std::string info_service_config_json_ ABSL_GUARDED_BY(info_mu_);
};
} // namespace grpc_core
#endif // GRPC_SRC_CORE_CLIENT_CHANNEL_CLIENT_CHANNEL_H

@ -1109,7 +1109,7 @@ class ClientChannelFilter::ClientChannelControlHelper final
const ChannelArgs& args) override
ABSL_EXCLUSIVE_LOCKS_REQUIRED(*chand_->work_serializer_) {
if (chand_->resolver_ == nullptr) return nullptr; // Shutting down.
ChannelArgs subchannel_args = ClientChannelFilter::MakeSubchannelArgs(
ChannelArgs subchannel_args = Subchannel::MakeSubchannelArgs(
args, per_address_args, chand_->subchannel_pool_,
chand_->default_authority_);
// Create subchannel.
@ -1359,31 +1359,6 @@ ClientChannelFilter::CreateLoadBalancedCallPromise(
return call_ptr->MakeCallPromise(std::move(call_args), std::move(lb_call));
}
ChannelArgs ClientChannelFilter::MakeSubchannelArgs(
const ChannelArgs& channel_args, const ChannelArgs& address_args,
const RefCountedPtr<SubchannelPoolInterface>& subchannel_pool,
const std::string& channel_default_authority) {
// Note that we start with the channel-level args and then apply the
// per-address args, so that if a value is present in both, the one
// in the channel-level args is used. This is particularly important
// for the GRPC_ARG_DEFAULT_AUTHORITY arg, which we want to allow
// resolvers to set on a per-address basis only if the application
// did not explicitly set it at the channel level.
return channel_args.UnionWith(address_args)
.SetObject(subchannel_pool)
// If we haven't already set the default authority arg (i.e., it
// was not explicitly set by the application nor overridden by
// the resolver), add it from the channel's default.
.SetIfUnset(GRPC_ARG_DEFAULT_AUTHORITY, channel_default_authority)
// Remove channel args that should not affect subchannel
// uniqueness.
.Remove(GRPC_ARG_HEALTH_CHECK_SERVICE_NAME)
.Remove(GRPC_ARG_INHIBIT_HEALTH_CHECKING)
.Remove(GRPC_ARG_CHANNELZ_CHANNEL_NODE)
// Remove all keys with the no-subchannel prefix.
.RemoveAllKeysWithPrefix(GRPC_ARG_NO_SUBCHANNEL_PREFIX);
}
void ClientChannelFilter::ReprocessQueuedResolverCalls() {
for (CallData* calld : resolver_queued_calls_) {
calld->RemoveCallFromResolverQueuedCallsLocked();

@ -86,9 +86,6 @@
// Channel arg key for server URI string.
#define GRPC_ARG_SERVER_URI "grpc.server_uri"
// Channel arg containing a pointer to the ClientChannelFilter object.
#define GRPC_ARG_CLIENT_CHANNEL "grpc.internal.client_channel_filter"
// Max number of batches that can be pending on a call at any given
// time. This includes one batch for each of the following ops:
// recv_initial_metadata
@ -112,7 +109,9 @@ class ClientChannelFilter final {
// Flag that this object gets stored in channel args as a raw pointer.
struct RawPointerChannelArgTag {};
static absl::string_view ChannelArgName() { return GRPC_ARG_CLIENT_CHANNEL; }
static absl::string_view ChannelArgName() {
return "grpc.internal.client_channel_filter";
}
static ArenaPromise<ServerMetadataHandle> MakeCallPromise(
grpc_channel_element* elem, CallArgs call_args,
@ -166,12 +165,6 @@ class ClientChannelFilter final {
CallArgs call_args, absl::AnyInvocable<void()> on_commit,
bool is_transparent_retry);
// Exposed for testing only.
static ChannelArgs MakeSubchannelArgs(
const ChannelArgs& channel_args, const ChannelArgs& address_args,
const RefCountedPtr<SubchannelPoolInterface>& subchannel_pool,
const std::string& channel_default_authority);
private:
class CallData;
class FilterBasedCallData;

@ -29,6 +29,7 @@
#include "src/core/lib/channel/context.h"
#include "src/core/lib/gprpp/unique_type_name.h"
#include "src/core/lib/resource_quota/arena.h"
#include "src/core/lib/transport/call_destination.h"
#include "src/core/load_balancing/lb_policy.h"
#include "src/core/service_config/service_config_call_data.h"
#include "src/core/telemetry/call_tracer.h"
@ -75,6 +76,13 @@ class ClientChannelServiceConfigCallData final : public ServiceConfigCallData {
absl::AnyInvocable<void()> on_commit_;
};
class SubchannelInterfaceWithCallDestination : public SubchannelInterface {
public:
using SubchannelInterface::SubchannelInterface;
// Obtain the call destination for this subchannel.
virtual RefCountedPtr<UnstartedCallDestination> call_destination() = 0;
};
} // namespace grpc_core
#endif // GRPC_SRC_CORE_CLIENT_CHANNEL_CLIENT_CHANNEL_INTERNAL_H

@ -0,0 +1,335 @@
// Copyright 2024 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 "src/core/client_channel/load_balanced_call_destination.h"
#include "src/core/client_channel/client_channel.h"
#include "src/core/client_channel/client_channel_internal.h"
#include "src/core/client_channel/subchannel.h"
#include "src/core/lib/channel/status_util.h"
#include "src/core/lib/promise/loop.h"
#include "src/core/telemetry/call_tracer.h"
namespace grpc_core {
// Defined in legacy client channel filter.
// TODO(roth): Move these here when we remove the legacy filter.
extern TraceFlag grpc_client_channel_trace;
extern TraceFlag grpc_client_channel_call_trace;
extern TraceFlag grpc_client_channel_lb_call_trace;
namespace {
class LbMetadata : public LoadBalancingPolicy::MetadataInterface {
public:
explicit LbMetadata(grpc_metadata_batch* batch) : batch_(batch) {}
void Add(absl::string_view key, absl::string_view value) override {
if (batch_ == nullptr) return;
// Gross, egregious hack to support legacy grpclb behavior.
// TODO(ctiller): Use a promise context for this once that plumbing is done.
if (key == GrpcLbClientStatsMetadata::key()) {
batch_->Set(
GrpcLbClientStatsMetadata(),
const_cast<GrpcLbClientStats*>(
reinterpret_cast<const GrpcLbClientStats*>(value.data())));
return;
}
batch_->Append(key, Slice::FromStaticString(value),
[key](absl::string_view error, const Slice& value) {
gpr_log(GPR_ERROR, "%s",
absl::StrCat(error, " key:", key,
" value:", value.as_string_view())
.c_str());
});
}
std::vector<std::pair<std::string, std::string>> TestOnlyCopyToVector()
override {
if (batch_ == nullptr) return {};
Encoder encoder;
batch_->Encode(&encoder);
return encoder.Take();
}
absl::optional<absl::string_view> Lookup(absl::string_view key,
std::string* buffer) const override {
if (batch_ == nullptr) return absl::nullopt;
return batch_->GetStringValue(key, buffer);
}
private:
class Encoder {
public:
void Encode(const Slice& key, const Slice& value) {
out_.emplace_back(std::string(key.as_string_view()),
std::string(value.as_string_view()));
}
template <class Which>
void Encode(Which, const typename Which::ValueType& value) {
auto value_slice = Which::Encode(value);
out_.emplace_back(std::string(Which::key()),
std::string(value_slice.as_string_view()));
}
void Encode(GrpcTimeoutMetadata,
const typename GrpcTimeoutMetadata::ValueType&) {}
void Encode(HttpPathMetadata, const Slice&) {}
void Encode(HttpMethodMetadata,
const typename HttpMethodMetadata::ValueType&) {}
std::vector<std::pair<std::string, std::string>> Take() {
return std::move(out_);
}
private:
std::vector<std::pair<std::string, std::string>> out_;
};
grpc_metadata_batch* batch_;
};
void MaybeCreateCallAttemptTracer(bool is_transparent_retry) {
auto* call_tracer = MaybeGetContext<ClientCallTracer>();
if (call_tracer == nullptr) return;
auto* tracer = call_tracer->StartNewAttempt(is_transparent_retry);
SetContext<CallTracerInterface>(tracer);
}
class LbCallState : public ClientChannelLbCallState {
public:
void* Alloc(size_t size) override { return GetContext<Arena>()->Alloc(size); }
// Internal API to allow first-party LB policies to access per-call
// attributes set by the ConfigSelector.
ServiceConfigCallData::CallAttributeInterface* GetCallAttribute(
UniqueTypeName type) const override {
auto* service_config_call_data = GetContext<ServiceConfigCallData>();
return service_config_call_data->GetCallAttribute(type);
}
ClientCallTracer::CallAttemptTracer* GetCallAttemptTracer() const override {
auto* legacy_context = GetContext<grpc_call_context_element>();
return static_cast<ClientCallTracer::CallAttemptTracer*>(
legacy_context[GRPC_CONTEXT_CALL_TRACER].value);
}
};
// TODO(roth): Remove this in favor of the gprpp Match() function once
// we can do that without breaking lock annotations.
template <typename T>
T HandlePickResult(
LoadBalancingPolicy::PickResult* result,
std::function<T(LoadBalancingPolicy::PickResult::Complete*)> complete_func,
std::function<T(LoadBalancingPolicy::PickResult::Queue*)> queue_func,
std::function<T(LoadBalancingPolicy::PickResult::Fail*)> fail_func,
std::function<T(LoadBalancingPolicy::PickResult::Drop*)> drop_func) {
auto* complete_pick =
absl::get_if<LoadBalancingPolicy::PickResult::Complete>(&result->result);
if (complete_pick != nullptr) {
return complete_func(complete_pick);
}
auto* queue_pick =
absl::get_if<LoadBalancingPolicy::PickResult::Queue>(&result->result);
if (queue_pick != nullptr) {
return queue_func(queue_pick);
}
auto* fail_pick =
absl::get_if<LoadBalancingPolicy::PickResult::Fail>(&result->result);
if (fail_pick != nullptr) {
return fail_func(fail_pick);
}
auto* drop_pick =
absl::get_if<LoadBalancingPolicy::PickResult::Drop>(&result->result);
CHECK(drop_pick != nullptr);
return drop_func(drop_pick);
}
// Does an LB pick for a call. Returns one of the following things:
// - Continue{}, meaning to queue the pick
// - a non-OK status, meaning to fail the call
// - a call destination, meaning that the pick is complete
// When the pick is complete, pushes client_initial_metadata onto
// call_initiator. Also adds the subchannel call tracker (if any) to
// context.
LoopCtl<absl::StatusOr<RefCountedPtr<UnstartedCallDestination>>> PickSubchannel(
LoadBalancingPolicy::SubchannelPicker& picker,
UnstartedCallHandler& unstarted_handler) {
// Perform LB pick.
auto& client_initial_metadata =
unstarted_handler.UnprocessedClientInitialMetadata();
LoadBalancingPolicy::PickArgs pick_args;
Slice* path = client_initial_metadata.get_pointer(HttpPathMetadata());
CHECK(path != nullptr);
pick_args.path = path->as_string_view();
LbCallState lb_call_state;
pick_args.call_state = &lb_call_state;
LbMetadata initial_metadata(&client_initial_metadata);
pick_args.initial_metadata = &initial_metadata;
auto result = picker.Pick(pick_args);
// Handle result.
return HandlePickResult<
LoopCtl<absl::StatusOr<RefCountedPtr<UnstartedCallDestination>>>>(
&result,
// CompletePick
[&](LoadBalancingPolicy::PickResult::Complete* complete_pick)
-> LoopCtl<absl::StatusOr<RefCountedPtr<UnstartedCallDestination>>> {
if (GRPC_TRACE_FLAG_ENABLED(grpc_client_channel_lb_call_trace)) {
gpr_log(GPR_INFO,
"client_channel: %sLB pick succeeded: subchannel=%p",
GetContext<Activity>()->DebugTag().c_str(),
complete_pick->subchannel.get());
}
CHECK(complete_pick->subchannel != nullptr);
// Grab a ref to the call destination while we're still
// holding the data plane mutex.
auto call_destination =
DownCast<SubchannelInterfaceWithCallDestination*>(
complete_pick->subchannel.get())
->call_destination();
// If the subchannel has no call destination (e.g., if the
// subchannel has moved out of state READY but the LB policy hasn't
// yet seen that change and given us a new picker), then just
// queue the pick. We'll try again as soon as we get a new picker.
if (call_destination == nullptr) {
if (GRPC_TRACE_FLAG_ENABLED(grpc_client_channel_lb_call_trace)) {
gpr_log(GPR_INFO,
"client_channel: %ssubchannel returned by LB picker "
"has no connected subchannel; queueing pick",
GetContext<Activity>()->DebugTag().c_str());
}
return Continue{};
}
// If the LB policy returned a call tracker, inform it that the
// call is starting and add it to context, so that we can notify
// it when the call finishes.
if (complete_pick->subchannel_call_tracker != nullptr) {
complete_pick->subchannel_call_tracker->Start();
SetContext(complete_pick->subchannel_call_tracker.release());
}
// Return the connected subchannel.
return call_destination;
},
// QueuePick
[&](LoadBalancingPolicy::PickResult::Queue* /*queue_pick*/) {
if (GRPC_TRACE_FLAG_ENABLED(grpc_client_channel_lb_call_trace)) {
gpr_log(GPR_INFO, "client_channel: %sLB pick queued",
GetContext<Activity>()->DebugTag().c_str());
}
return Continue{};
},
// FailPick
[&](LoadBalancingPolicy::PickResult::Fail* fail_pick)
-> LoopCtl<absl::StatusOr<RefCountedPtr<UnstartedCallDestination>>> {
if (GRPC_TRACE_FLAG_ENABLED(grpc_client_channel_lb_call_trace)) {
gpr_log(GPR_INFO, "client_channel: %sLB pick failed: %s",
GetContext<Activity>()->DebugTag().c_str(),
fail_pick->status.ToString().c_str());
}
// If wait_for_ready is false, then the error indicates the RPC
// attempt's final status.
if (!unstarted_handler.UnprocessedClientInitialMetadata()
.GetOrCreatePointer(WaitForReady())
->value) {
return MaybeRewriteIllegalStatusCode(std::move(fail_pick->status),
"LB pick");
}
// If wait_for_ready is true, then queue to retry when we get a new
// picker.
return Continue{};
},
// DropPick
[&](LoadBalancingPolicy::PickResult::Drop* drop_pick)
-> LoopCtl<absl::StatusOr<RefCountedPtr<UnstartedCallDestination>>> {
if (GRPC_TRACE_FLAG_ENABLED(grpc_client_channel_lb_call_trace)) {
gpr_log(GPR_INFO, "client_channel: %sLB pick dropped: %s",
GetContext<Activity>()->DebugTag().c_str(),
drop_pick->status.ToString().c_str());
}
return grpc_error_set_int(MaybeRewriteIllegalStatusCode(
std::move(drop_pick->status), "LB drop"),
StatusIntProperty::kLbPolicyDrop, 1);
});
}
} // namespace
void LoadBalancedCallDestination::StartCall(
UnstartedCallHandler unstarted_handler) {
// If there is a call tracer, create a call attempt tracer.
bool* is_transparent_retry_metadata =
unstarted_handler.UnprocessedClientInitialMetadata().get_pointer(
IsTransparentRetry());
bool is_transparent_retry = is_transparent_retry_metadata != nullptr
? *is_transparent_retry_metadata
: false;
MaybeCreateCallAttemptTracer(is_transparent_retry);
// Spawn a promise to do the LB pick.
// This will eventually start the call.
unstarted_handler.SpawnGuardedUntilCallCompletes(
"lb_pick", [unstarted_handler, picker = picker_]() mutable {
return Map(
// Wait for the LB picker.
CheckDelayed(Loop(
[last_picker =
RefCountedPtr<LoadBalancingPolicy::SubchannelPicker>(),
unstarted_handler, picker]() mutable {
return Map(
picker.Next(last_picker),
[unstarted_handler, &last_picker](
RefCountedPtr<LoadBalancingPolicy::SubchannelPicker>
picker) mutable {
last_picker = std::move(picker);
// Returns 3 possible things:
// - Continue to queue the pick
// - non-OK status to fail the pick
// - a connected subchannel to complete the pick
return PickSubchannel(*last_picker, unstarted_handler);
});
})),
// Create call stack on the connected subchannel.
[unstarted_handler](
std::tuple<
absl::StatusOr<RefCountedPtr<UnstartedCallDestination>>,
bool>
pick_result) {
auto& call_destination = std::get<0>(pick_result);
const bool was_queued = std::get<1>(pick_result);
if (!call_destination.ok()) {
return call_destination.status();
}
// LB pick is done, so indicate that we've committed.
auto* on_commit = MaybeGetContext<LbOnCommit>();
if (on_commit != nullptr && *on_commit != nullptr) {
(*on_commit)();
}
// If it was queued, add a trace annotation.
if (was_queued) {
auto* tracer =
MaybeGetContext<ClientCallTracer::CallAttemptTracer>();
if (tracer != nullptr) {
tracer->RecordAnnotation("Delayed LB pick complete.");
}
}
// Delegate to connected subchannel.
// TODO(ctiller): need to insert LbCallTracingFilter at the top of
// the stack
(*call_destination)->StartCall(unstarted_handler);
return absl::OkStatus();
});
});
}
} // namespace grpc_core

@ -0,0 +1,49 @@
// Copyright 2024 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_SRC_CORE_CLIENT_CHANNEL_LOAD_BALANCED_CALL_DESTINATION_H
#define GRPC_SRC_CORE_CLIENT_CHANNEL_LOAD_BALANCED_CALL_DESTINATION_H
#include "absl/functional/any_invocable.h"
#include "src/core/client_channel/client_channel.h"
#include "src/core/lib/promise/context.h"
#include "src/core/lib/transport/call_destination.h"
#include "src/core/load_balancing/lb_policy.h"
namespace grpc_core {
// Context type for LB on_commit callback.
// TODO(ctiller): make this a struct, so we don't accidentally alias context
// types
using LbOnCommit = absl::AnyInvocable<void()>;
template <>
struct ContextType<LbOnCommit> {};
class LoadBalancedCallDestination final : public UnstartedCallDestination {
public:
explicit LoadBalancedCallDestination(ClientChannel::PickerObservable picker)
: picker_(std::move(picker)) {}
void Orphaned() override {}
void StartCall(UnstartedCallHandler unstarted_handler) override;
private:
ClientChannel::PickerObservable picker_;
};
} // namespace grpc_core
#endif // GRPC_SRC_CORE_CLIENT_CHANNEL_LOAD_BALANCED_CALL_DESTINATION_H

@ -41,6 +41,7 @@
#include "src/core/channelz/channel_trace.h"
#include "src/core/channelz/channelz.h"
#include "src/core/client_channel/client_channel_internal.h"
#include "src/core/client_channel/subchannel_pool_interface.h"
#include "src/core/handshaker/proxy_mapper_registry.h"
#include "src/core/lib/address_utils/sockaddr_utils.h"
@ -50,7 +51,10 @@
#include "src/core/lib/channel/channel_stack_builder_impl.h"
#include "src/core/lib/config/core_configuration.h"
#include "src/core/lib/debug/trace.h"
#include "src/core/lib/experiments/experiments.h"
#include "src/core/lib/gprpp/debug_location.h"
#include "src/core/lib/gprpp/orphanable.h"
#include "src/core/lib/gprpp/ref_counted.h"
#include "src/core/lib/gprpp/ref_counted_ptr.h"
#include "src/core/lib/gprpp/status_helper.h"
#include "src/core/lib/gprpp/sync.h"
@ -64,6 +68,7 @@
#include "src/core/lib/surface/init_internally.h"
#include "src/core/lib/transport/connectivity_state.h"
#include "src/core/lib/transport/error_utils.h"
#include "src/core/lib/transport/interception_chain.h"
#include "src/core/lib/transport/transport.h"
#include "src/core/telemetry/stats.h"
#include "src/core/telemetry/stats_data.h"
@ -97,75 +102,168 @@ DebugOnlyTraceFlag grpc_trace_subchannel_refcount(false, "subchannel_refcount");
//
ConnectedSubchannel::ConnectedSubchannel(
grpc_channel_stack* channel_stack, const ChannelArgs& args,
const ChannelArgs& args,
RefCountedPtr<channelz::SubchannelNode> channelz_subchannel)
: RefCounted<ConnectedSubchannel>(
GRPC_TRACE_FLAG_ENABLED(grpc_trace_subchannel_refcount)
? "ConnectedSubchannel"
: nullptr),
channel_stack_(channel_stack),
args_(args),
channelz_subchannel_(std::move(channelz_subchannel)) {}
ConnectedSubchannel::~ConnectedSubchannel() {
GRPC_CHANNEL_STACK_UNREF(channel_stack_, "connected_subchannel_dtor");
}
void ConnectedSubchannel::StartWatch(
grpc_pollset_set* interested_parties,
OrphanablePtr<ConnectivityStateWatcherInterface> watcher) {
grpc_transport_op* op = grpc_make_transport_op(nullptr);
op->start_connectivity_watch = std::move(watcher);
op->start_connectivity_watch_state = GRPC_CHANNEL_READY;
op->bind_pollset_set = interested_parties;
grpc_channel_element* elem = grpc_channel_stack_element(channel_stack_, 0);
elem->filter->start_transport_op(elem, op);
}
void ConnectedSubchannel::Ping(grpc_closure* on_initiate,
grpc_closure* on_ack) {
grpc_transport_op* op = grpc_make_transport_op(nullptr);
grpc_channel_element* elem;
op->send_ping.on_initiate = on_initiate;
op->send_ping.on_ack = on_ack;
elem = grpc_channel_stack_element(channel_stack_, 0);
elem->filter->start_transport_op(elem, op);
}
size_t ConnectedSubchannel::GetInitialCallSizeEstimate() const {
return GPR_ROUND_UP_TO_ALIGNMENT_SIZE(sizeof(SubchannelCall)) +
channel_stack_->call_stack_size;
}
ArenaPromise<ServerMetadataHandle> ConnectedSubchannel::MakeCallPromise(
CallArgs call_args) {
// If not using channelz, we just need to call the channel stack.
if (channelz_subchannel() == nullptr) {
return channel_stack_->MakeClientCallPromise(std::move(call_args));
}
// Otherwise, we need to wrap the channel stack promise with code that
// handles the channelz updates.
return OnCancel(
Seq(channel_stack_->MakeClientCallPromise(std::move(call_args)),
[self = Ref()](ServerMetadataHandle metadata) {
channelz::SubchannelNode* channelz_subchannel =
self->channelz_subchannel();
CHECK_NE(channelz_subchannel, nullptr);
if (metadata->get(GrpcStatusMetadata())
.value_or(GRPC_STATUS_UNKNOWN) != GRPC_STATUS_OK) {
channelz_subchannel->RecordCallFailed();
} else {
channelz_subchannel->RecordCallSucceeded();
}
return metadata;
}),
[self = Ref()]() {
channelz::SubchannelNode* channelz_subchannel =
self->channelz_subchannel();
CHECK_NE(channelz_subchannel, nullptr);
channelz_subchannel->RecordCallFailed();
});
}
//
// LegacyConnectedSubchannel
//
class LegacyConnectedSubchannel : public ConnectedSubchannel {
public:
LegacyConnectedSubchannel(
RefCountedPtr<grpc_channel_stack> channel_stack, const ChannelArgs& args,
RefCountedPtr<channelz::SubchannelNode> channelz_subchannel)
: ConnectedSubchannel(args, std::move(channelz_subchannel)),
channel_stack_(std::move(channel_stack)) {}
~LegacyConnectedSubchannel() override {
channel_stack_.reset(DEBUG_LOCATION, "ConnectedSubchannel");
}
void StartWatch(
grpc_pollset_set* interested_parties,
OrphanablePtr<ConnectivityStateWatcherInterface> watcher) override {
grpc_transport_op* op = grpc_make_transport_op(nullptr);
op->start_connectivity_watch = std::move(watcher);
op->start_connectivity_watch_state = GRPC_CHANNEL_READY;
op->bind_pollset_set = interested_parties;
grpc_channel_element* elem =
grpc_channel_stack_element(channel_stack_.get(), 0);
elem->filter->start_transport_op(elem, op);
}
void Ping(absl::AnyInvocable<void(absl::Status)>) override {
Crash("call v3 ping method called in legacy impl");
}
RefCountedPtr<UnstartedCallDestination> unstarted_call_destination()
const override {
Crash("call v3 unstarted_call_destination method called in legacy impl");
}
grpc_channel_stack* channel_stack() const override {
return channel_stack_.get();
}
size_t GetInitialCallSizeEstimate() const override {
return GPR_ROUND_UP_TO_ALIGNMENT_SIZE(sizeof(SubchannelCall)) +
channel_stack_->call_stack_size;
}
ArenaPromise<ServerMetadataHandle> MakeCallPromise(
CallArgs call_args) override {
// If not using channelz, we just need to call the channel stack.
if (channelz_subchannel() == nullptr) {
return channel_stack_->MakeClientCallPromise(std::move(call_args));
}
// Otherwise, we need to wrap the channel stack promise with code that
// handles the channelz updates.
return OnCancel(
Seq(channel_stack_->MakeClientCallPromise(std::move(call_args)),
[self = Ref()](ServerMetadataHandle metadata) {
channelz::SubchannelNode* channelz_subchannel =
self->channelz_subchannel();
CHECK(channelz_subchannel != nullptr);
if (metadata->get(GrpcStatusMetadata())
.value_or(GRPC_STATUS_UNKNOWN) != GRPC_STATUS_OK) {
channelz_subchannel->RecordCallFailed();
} else {
channelz_subchannel->RecordCallSucceeded();
}
return metadata;
}),
[self = Ref()]() {
channelz::SubchannelNode* channelz_subchannel =
self->channelz_subchannel();
CHECK(channelz_subchannel != nullptr);
channelz_subchannel->RecordCallFailed();
});
}
void Ping(grpc_closure* on_initiate, grpc_closure* on_ack) override {
grpc_transport_op* op = grpc_make_transport_op(nullptr);
op->send_ping.on_initiate = on_initiate;
op->send_ping.on_ack = on_ack;
grpc_channel_element* elem =
grpc_channel_stack_element(channel_stack_.get(), 0);
elem->filter->start_transport_op(elem, op);
}
private:
RefCountedPtr<grpc_channel_stack> channel_stack_;
};
//
// NewConnectedSubchannel
//
class NewConnectedSubchannel : public ConnectedSubchannel {
public:
class TransportCallDestination final : public CallDestination {
public:
explicit TransportCallDestination(OrphanablePtr<ClientTransport> transport)
: transport_(std::move(transport)) {}
ClientTransport* transport() { return transport_.get(); }
void HandleCall(CallHandler handler) override {
transport_->StartCall(std::move(handler));
}
void Orphaned() override { transport_.reset(); }
private:
OrphanablePtr<ClientTransport> transport_;
};
NewConnectedSubchannel(
RefCountedPtr<UnstartedCallDestination> call_destination,
RefCountedPtr<TransportCallDestination> transport,
const ChannelArgs& args,
RefCountedPtr<channelz::SubchannelNode> channelz_subchannel)
: ConnectedSubchannel(args, std::move(channelz_subchannel)),
call_destination_(std::move(call_destination)),
transport_(std::move(transport)) {}
void StartWatch(
grpc_pollset_set*,
OrphanablePtr<ConnectivityStateWatcherInterface> watcher) override {
transport_->transport()->StartConnectivityWatch(std::move(watcher));
}
void Ping(absl::AnyInvocable<void(absl::Status)>) override {
// TODO(ctiller): add new transport API for this in v3 stack
Crash("not implemented");
}
RefCountedPtr<UnstartedCallDestination> unstarted_call_destination()
const override {
return call_destination_;
}
grpc_channel_stack* channel_stack() const override { return nullptr; }
size_t GetInitialCallSizeEstimate() const override { return 0; }
ArenaPromise<ServerMetadataHandle> MakeCallPromise(CallArgs) override {
Crash("legacy MakeCallPromise() method called in call v3 impl");
}
void Ping(grpc_closure*, grpc_closure*) override {
Crash("legacy ping method called in call v3 impl");
}
private:
RefCountedPtr<UnstartedCallDestination> call_destination_;
RefCountedPtr<TransportCallDestination> transport_;
};
//
// SubchannelCall
@ -255,8 +353,8 @@ void SubchannelCall::Destroy(void* arg, grpc_error_handle /*error*/) {
// Destroy the subchannel call.
self->~SubchannelCall();
// Destroy the call stack. This should be after destroying the subchannel
// call, because call->after_call_stack_destroy(), if not null, will free the
// call arena.
// call, because call->after_call_stack_destroy(), if not null, will free
// the call arena.
grpc_call_stack_destroy(SUBCHANNEL_CALL_TO_CALL_STACK(self), nullptr,
after_call_stack_destroy);
// Automatically reset connected_subchannel. This should be after destroying
@ -474,8 +572,8 @@ Subchannel::Subchannel(SubchannelKey key,
// result the subchannel destruction happens asynchronously to channel
// destruction. If the last channel destruction triggers a grpc_shutdown
// before the last subchannel destruction, then there maybe race conditions
// triggering segmentation faults. To prevent this issue, we call a grpc_init
// here and a grpc_shutdown in the subchannel destructor.
// triggering segmentation faults. To prevent this issue, we call a
// grpc_init here and a grpc_shutdown in the subchannel destructor.
InitInternally();
global_stats().IncrementClientSubchannelsCreated();
GRPC_CLOSURE_INIT(&on_connecting_finished_, OnConnectingFinished, this,
@ -587,7 +685,8 @@ void Subchannel::CancelConnectivityStateWatch(
watcher_list_.RemoveWatcherLocked(watcher);
}
// Drain any connectivity state notifications after releasing the mutex.
// (Shouldn't actually be necessary in this case, but better safe than sorry.)
// (Shouldn't actually be necessary in this case, but better safe than
// sorry.)
work_serializer_.DrainQueue();
}
@ -758,11 +857,11 @@ void Subchannel::OnConnectingFinishedLocked(grpc_error_handle error) {
ExecCtx exec_ctx;
self->OnRetryTimer();
// Subchannel deletion might require an active ExecCtx. So if
// self.reset() is not called here, the WeakRefCountedPtr destructor
// may run after the ExecCtx declared in the callback is destroyed.
// Since subchannel may get destroyed when the WeakRefCountedPtr
// destructor runs, it may not have an active ExecCtx - thus leading
// to crashes.
// self.reset() is not called here, the WeakRefCountedPtr
// destructor may run after the ExecCtx declared in the callback
// is destroyed. Since subchannel may get destroyed when the
// WeakRefCountedPtr destructor runs, it may not have an active
// ExecCtx - thus leading to crashes.
self.reset();
}
});
@ -770,37 +869,60 @@ void Subchannel::OnConnectingFinishedLocked(grpc_error_handle error) {
}
bool Subchannel::PublishTransportLocked() {
// Construct channel stack.
// Builder takes ownership of transport.
ChannelStackBuilderImpl builder(
"subchannel", GRPC_CLIENT_SUBCHANNEL,
connecting_result_.channel_args.SetObject(
std::exchange(connecting_result_.transport, nullptr)));
if (!CoreConfiguration::Get().channel_init().CreateStack(&builder)) {
return false;
}
absl::StatusOr<RefCountedPtr<grpc_channel_stack>> stk = builder.Build();
if (!stk.ok()) {
auto error = absl_status_to_grpc_error(stk.status());
connecting_result_.Reset();
gpr_log(GPR_ERROR,
"subchannel %p %s: error initializing subchannel stack: %s", this,
key_.ToString().c_str(), StatusToString(error).c_str());
return false;
auto socket_node = std::move(connecting_result_.socket_node);
if (connecting_result_.transport->filter_stack_transport() != nullptr ||
IsChaoticGoodEnabled()) {
// Construct channel stack.
// Builder takes ownership of transport.
ChannelStackBuilderImpl builder(
"subchannel", GRPC_CLIENT_SUBCHANNEL,
connecting_result_.channel_args.SetObject(
std::exchange(connecting_result_.transport, nullptr)));
if (!CoreConfiguration::Get().channel_init().CreateStack(&builder)) {
return false;
}
absl::StatusOr<RefCountedPtr<grpc_channel_stack>> stack = builder.Build();
if (!stack.ok()) {
connecting_result_.Reset();
gpr_log(GPR_ERROR,
"subchannel %p %s: error initializing subchannel stack: %s", this,
key_.ToString().c_str(), stack.status().ToString().c_str());
return false;
}
connected_subchannel_ = MakeRefCounted<LegacyConnectedSubchannel>(
std::move(*stack), args_, channelz_node_);
} else {
OrphanablePtr<ClientTransport> transport(
std::exchange(connecting_result_.transport, nullptr)
->client_transport());
InterceptionChainBuilder builder(
connecting_result_.channel_args.SetObject(transport.get()));
CoreConfiguration::Get().channel_init().AddToInterceptionChainBuilder(
GRPC_CLIENT_SUBCHANNEL, builder);
auto transport_destination =
MakeRefCounted<NewConnectedSubchannel::TransportCallDestination>(
std::move(transport));
auto call_destination = builder.Build(transport_destination);
if (!call_destination.ok()) {
connecting_result_.Reset();
gpr_log(GPR_ERROR,
"subchannel %p %s: error initializing subchannel stack: %s", this,
key_.ToString().c_str(),
call_destination.status().ToString().c_str());
return false;
}
connected_subchannel_ = MakeRefCounted<NewConnectedSubchannel>(
std::move(*call_destination), std::move(transport_destination), args_,
channelz_node_);
}
RefCountedPtr<channelz::SocketNode> socket =
std::move(connecting_result_.socket_node);
connecting_result_.Reset();
if (shutdown_) return false;
// Publish.
connected_subchannel_.reset(
new ConnectedSubchannel(stk->release(), args_, channelz_node_));
if (GRPC_TRACE_FLAG_ENABLED(grpc_trace_subchannel)) {
gpr_log(GPR_INFO, "subchannel %p %s: new connected subchannel at %p", this,
key_.ToString().c_str(), connected_subchannel_.get());
}
if (channelz_node_ != nullptr) {
channelz_node_->SetChildSocket(std::move(socket));
channelz_node_->SetChildSocket(std::move(socket_node));
}
// Start watching connected subchannel.
connected_subchannel_->StartWatch(
@ -811,4 +933,29 @@ bool Subchannel::PublishTransportLocked() {
return true;
}
ChannelArgs Subchannel::MakeSubchannelArgs(
const ChannelArgs& channel_args, const ChannelArgs& address_args,
const RefCountedPtr<SubchannelPoolInterface>& subchannel_pool,
const std::string& channel_default_authority) {
// Note that we start with the channel-level args and then apply the
// per-address args, so that if a value is present in both, the one
// in the channel-level args is used. This is particularly important
// for the GRPC_ARG_DEFAULT_AUTHORITY arg, which we want to allow
// resolvers to set on a per-address basis only if the application
// did not explicitly set it at the channel level.
return channel_args.UnionWith(address_args)
.SetObject(subchannel_pool)
// If we haven't already set the default authority arg (i.e., it
// was not explicitly set by the application nor overridden by
// the resolver), add it from the channel's default.
.SetIfUnset(GRPC_ARG_DEFAULT_AUTHORITY, channel_default_authority)
// Remove channel args that should not affect subchannel
// uniqueness.
.Remove(GRPC_ARG_HEALTH_CHECK_SERVICE_NAME)
.Remove(GRPC_ARG_INHIBIT_HEALTH_CHECKING)
.Remove(GRPC_ARG_CHANNELZ_CHANNEL_NODE)
// Remove all keys with the no-subchannel prefix.
.RemoveAllKeysWithPrefix(GRPC_ARG_NO_SUBCHANNEL_PREFIX);
}
} // namespace grpc_core

@ -64,30 +64,35 @@ namespace grpc_core {
class SubchannelCall;
class ConnectedSubchannel final : public RefCounted<ConnectedSubchannel> {
class ConnectedSubchannel : public RefCounted<ConnectedSubchannel> {
public:
ConnectedSubchannel(
grpc_channel_stack* channel_stack, const ChannelArgs& args,
RefCountedPtr<channelz::SubchannelNode> channelz_subchannel);
~ConnectedSubchannel() override;
void StartWatch(grpc_pollset_set* interested_parties,
OrphanablePtr<ConnectivityStateWatcherInterface> watcher);
void Ping(grpc_closure* on_initiate, grpc_closure* on_ack);
grpc_channel_stack* channel_stack() const { return channel_stack_; }
const ChannelArgs& args() const { return args_; }
channelz::SubchannelNode* channelz_subchannel() const {
return channelz_subchannel_.get();
}
size_t GetInitialCallSizeEstimate() const;
virtual void StartWatch(
grpc_pollset_set* interested_parties,
OrphanablePtr<ConnectivityStateWatcherInterface> watcher) = 0;
// Methods for v3 stack.
virtual void Ping(absl::AnyInvocable<void(absl::Status)> on_ack) = 0;
virtual RefCountedPtr<UnstartedCallDestination> unstarted_call_destination()
const = 0;
ArenaPromise<ServerMetadataHandle> MakeCallPromise(CallArgs call_args);
// Methods for legacy stack.
virtual grpc_channel_stack* channel_stack() const = 0;
virtual size_t GetInitialCallSizeEstimate() const = 0;
virtual ArenaPromise<ServerMetadataHandle> MakeCallPromise(
CallArgs call_args) = 0;
virtual void Ping(grpc_closure* on_initiate, grpc_closure* on_ack) = 0;
protected:
ConnectedSubchannel(
const ChannelArgs& args,
RefCountedPtr<channelz::SubchannelNode> channelz_subchannel);
private:
grpc_channel_stack* channel_stack_;
ChannelArgs args_;
// ref counted pointer to the channelz node in this connected subchannel's
// owning subchannel.
@ -243,6 +248,12 @@ class Subchannel final : public DualRefCounted<Subchannel> {
return connected_subchannel_;
}
RefCountedPtr<UnstartedCallDestination> call_destination() {
MutexLock lock(&mu_);
if (connected_subchannel_ == nullptr) return nullptr;
return connected_subchannel_->unstarted_call_destination();
}
// Attempt to connect to the backend. Has no effect if already connected.
void RequestConnection() ABSL_LOCKS_EXCLUDED(mu_);
@ -272,6 +283,12 @@ class Subchannel final : public DualRefCounted<Subchannel> {
return event_engine_;
}
// Exposed for testing purposes only.
static ChannelArgs MakeSubchannelArgs(
const ChannelArgs& channel_args, const ChannelArgs& address_args,
const RefCountedPtr<SubchannelPoolInterface>& subchannel_pool,
const std::string& channel_default_authority);
private:
// Tears down any existing connection, and arranges for destruction
void Orphaned() override ABSL_LOCKS_EXCLUDED(mu_);

@ -78,15 +78,11 @@ TraceFlag grpc_trace_client_idle_filter(false, "client_idle_filter");
} \
} while (0)
namespace {
Duration GetClientIdleTimeout(const ChannelArgs& args) {
return args.GetDurationFromIntMillis(GRPC_ARG_CLIENT_IDLE_TIMEOUT_MS)
.value_or(kDefaultIdleTimeout);
}
} // namespace
struct LegacyMaxAgeFilter::Config {
Duration max_connection_age;
Duration max_connection_idle;

@ -40,6 +40,8 @@
namespace grpc_core {
Duration GetClientIdleTimeout(const ChannelArgs& args);
class LegacyChannelIdleFilter : public ChannelFilter {
public:
LegacyChannelIdleFilter(grpc_channel_stack* channel_stack,

@ -83,6 +83,16 @@ inline int PointerCompare(void* a_ptr, const grpc_arg_pointer_vtable* a_vtable,
// before the crt refcount base class.
template <typename T>
using RefType = absl::remove_cvref_t<decltype(*std::declval<T>().Ref())>;
template <typename T, typename Ignored = void /* for SFINAE */>
struct IsRawPointerTagged {
static constexpr bool kValue = false;
};
template <typename T>
struct IsRawPointerTagged<T,
absl::void_t<typename T::RawPointerChannelArgTag>> {
static constexpr bool kValue = true;
};
} // namespace channel_args_detail
// Specialization for ref-counted pointers.
@ -91,13 +101,14 @@ using RefType = absl::remove_cvref_t<decltype(*std::declval<T>().Ref())>;
template <typename T>
struct ChannelArgTypeTraits<
T, absl::enable_if_t<
std::is_base_of<RefCounted<channel_args_detail::RefType<T>>,
channel_args_detail::RefType<T>>::value ||
std::is_base_of<RefCounted<channel_args_detail::RefType<T>,
NonPolymorphicRefCount>,
channel_args_detail::RefType<T>>::value ||
std::is_base_of<DualRefCounted<channel_args_detail::RefType<T>>,
channel_args_detail::RefType<T>>::value,
!channel_args_detail::IsRawPointerTagged<T>::kValue &&
(std::is_base_of<RefCounted<channel_args_detail::RefType<T>>,
channel_args_detail::RefType<T>>::value ||
std::is_base_of<RefCounted<channel_args_detail::RefType<T>,
NonPolymorphicRefCount>,
channel_args_detail::RefType<T>>::value ||
std::is_base_of<DualRefCounted<channel_args_detail::RefType<T>>,
channel_args_detail::RefType<T>>::value),
void>> {
static const grpc_arg_pointer_vtable* VTable() {
static const grpc_arg_pointer_vtable tbl = {

@ -230,6 +230,7 @@ struct grpc_channel_stack {
// full C++-ification for now.
void IncrementRefCount();
void Unref();
void Unref(const grpc_core::DebugLocation& location, const char* reason);
grpc_core::RefCountedPtr<grpc_channel_stack> Ref() {
IncrementRefCount();
return grpc_core::RefCountedPtr<grpc_channel_stack>(this);
@ -345,6 +346,11 @@ inline void grpc_channel_stack::Unref() {
GRPC_CHANNEL_STACK_UNREF(this, "smart_pointer");
}
inline void grpc_channel_stack::Unref(const grpc_core::DebugLocation&,
const char* reason) {
GRPC_CHANNEL_STACK_UNREF(this, reason);
}
inline void grpc_call_stack::IncrementRefCount() {
GRPC_CALL_STACK_REF(this, "smart_pointer");
}

@ -59,6 +59,9 @@ typedef enum {
/// the server.
GRPC_CONTEXT_BACKEND_METRIC_PROVIDER,
/// A LoadBalancingPolicy::SubchannelCallTrackerInterface
GRPC_SUBCHANNEL_CALL_TRACKER_INTERFACE,
/// Special Google context
GRPC_CONTEXT_GOOGLE,
@ -73,6 +76,8 @@ struct grpc_call_context_element {
namespace grpc_core {
class Call;
class CallTracerAnnotationInterface;
class CallTracerInterface;
class ServiceConfigCallData;
// Bind the legacy context array into the new style structure
// TODO(ctiller): remove as we migrate these contexts to the new system.
@ -96,6 +101,17 @@ struct OldStyleContext<CallTracerAnnotationInterface> {
GRPC_CONTEXT_CALL_TRACER_ANNOTATION_INTERFACE;
};
template <>
struct OldStyleContext<CallTracerInterface> {
static constexpr grpc_context_index kIndex = GRPC_CONTEXT_CALL_TRACER;
};
template <>
struct OldStyleContext<ServiceConfigCallData> {
static constexpr grpc_context_index kIndex =
GRPC_CONTEXT_SERVICE_CONFIG_CALL_DATA;
};
template <typename T>
class Context<T, absl::void_t<decltype(OldStyleContext<T>::kIndex)>> {
public:
@ -104,6 +120,15 @@ class Context<T, absl::void_t<decltype(OldStyleContext<T>::kIndex)>> {
GetContext<grpc_call_context_element>()[OldStyleContext<T>::kIndex]
.value);
}
static void set(T* value) {
auto& elem =
GetContext<grpc_call_context_element>()[OldStyleContext<T>::kIndex];
if (elem.destroy != nullptr) {
elem.destroy(elem.value);
elem.destroy = nullptr;
}
elem.value = value;
}
};
} // namespace promise_detail

@ -29,8 +29,6 @@ const char* const description_call_status_override_on_cancellation =
"with cancellation.";
const char* const additional_constraints_call_status_override_on_cancellation =
"{}";
const char* const description_call_v3 = "Promise-based call version 3.";
const char* const additional_constraints_call_v3 = "{}";
const char* const description_canary_client_privacy =
"If set, canary client privacy";
const char* const additional_constraints_canary_client_privacy = "{}";
@ -133,6 +131,12 @@ const char* const description_work_serializer_dispatch =
const char* const additional_constraints_work_serializer_dispatch = "{}";
const uint8_t required_experiments_work_serializer_dispatch[] = {
static_cast<uint8_t>(grpc_core::kExperimentIdEventEngineClient)};
const char* const description_call_v3 = "Promise-based call version 3.";
const char* const additional_constraints_call_v3 = "{}";
const uint8_t required_experiments_call_v3[] = {
static_cast<uint8_t>(grpc_core::kExperimentIdEventEngineClient),
static_cast<uint8_t>(grpc_core::kExperimentIdEventEngineListener),
static_cast<uint8_t>(grpc_core::kExperimentIdWorkSerializerDispatch)};
} // namespace
namespace grpc_core {
@ -142,8 +146,6 @@ const ExperimentMetadata g_experiment_metadata[] = {
description_call_status_override_on_cancellation,
additional_constraints_call_status_override_on_cancellation, nullptr, 0,
true, true},
{"call_v3", description_call_v3, additional_constraints_call_v3, nullptr, 0,
false, true},
{"canary_client_privacy", description_canary_client_privacy,
additional_constraints_canary_client_privacy, nullptr, 0, false, false},
{"client_privacy", description_client_privacy,
@ -208,6 +210,8 @@ const ExperimentMetadata g_experiment_metadata[] = {
{"work_serializer_dispatch", description_work_serializer_dispatch,
additional_constraints_work_serializer_dispatch,
required_experiments_work_serializer_dispatch, 1, false, true},
{"call_v3", description_call_v3, additional_constraints_call_v3,
required_experiments_call_v3, 3, false, false},
};
} // namespace grpc_core
@ -219,8 +223,6 @@ const char* const description_call_status_override_on_cancellation =
"with cancellation.";
const char* const additional_constraints_call_status_override_on_cancellation =
"{}";
const char* const description_call_v3 = "Promise-based call version 3.";
const char* const additional_constraints_call_v3 = "{}";
const char* const description_canary_client_privacy =
"If set, canary client privacy";
const char* const additional_constraints_canary_client_privacy = "{}";
@ -323,6 +325,12 @@ const char* const description_work_serializer_dispatch =
const char* const additional_constraints_work_serializer_dispatch = "{}";
const uint8_t required_experiments_work_serializer_dispatch[] = {
static_cast<uint8_t>(grpc_core::kExperimentIdEventEngineClient)};
const char* const description_call_v3 = "Promise-based call version 3.";
const char* const additional_constraints_call_v3 = "{}";
const uint8_t required_experiments_call_v3[] = {
static_cast<uint8_t>(grpc_core::kExperimentIdEventEngineClient),
static_cast<uint8_t>(grpc_core::kExperimentIdEventEngineListener),
static_cast<uint8_t>(grpc_core::kExperimentIdWorkSerializerDispatch)};
} // namespace
namespace grpc_core {
@ -332,8 +340,6 @@ const ExperimentMetadata g_experiment_metadata[] = {
description_call_status_override_on_cancellation,
additional_constraints_call_status_override_on_cancellation, nullptr, 0,
true, true},
{"call_v3", description_call_v3, additional_constraints_call_v3, nullptr, 0,
false, true},
{"canary_client_privacy", description_canary_client_privacy,
additional_constraints_canary_client_privacy, nullptr, 0, false, false},
{"client_privacy", description_client_privacy,
@ -398,6 +404,8 @@ const ExperimentMetadata g_experiment_metadata[] = {
{"work_serializer_dispatch", description_work_serializer_dispatch,
additional_constraints_work_serializer_dispatch,
required_experiments_work_serializer_dispatch, 1, false, true},
{"call_v3", description_call_v3, additional_constraints_call_v3,
required_experiments_call_v3, 3, false, false},
};
} // namespace grpc_core
@ -409,8 +417,6 @@ const char* const description_call_status_override_on_cancellation =
"with cancellation.";
const char* const additional_constraints_call_status_override_on_cancellation =
"{}";
const char* const description_call_v3 = "Promise-based call version 3.";
const char* const additional_constraints_call_v3 = "{}";
const char* const description_canary_client_privacy =
"If set, canary client privacy";
const char* const additional_constraints_canary_client_privacy = "{}";
@ -513,6 +519,12 @@ const char* const description_work_serializer_dispatch =
const char* const additional_constraints_work_serializer_dispatch = "{}";
const uint8_t required_experiments_work_serializer_dispatch[] = {
static_cast<uint8_t>(grpc_core::kExperimentIdEventEngineClient)};
const char* const description_call_v3 = "Promise-based call version 3.";
const char* const additional_constraints_call_v3 = "{}";
const uint8_t required_experiments_call_v3[] = {
static_cast<uint8_t>(grpc_core::kExperimentIdEventEngineClient),
static_cast<uint8_t>(grpc_core::kExperimentIdEventEngineListener),
static_cast<uint8_t>(grpc_core::kExperimentIdWorkSerializerDispatch)};
} // namespace
namespace grpc_core {
@ -522,8 +534,6 @@ const ExperimentMetadata g_experiment_metadata[] = {
description_call_status_override_on_cancellation,
additional_constraints_call_status_override_on_cancellation, nullptr, 0,
true, true},
{"call_v3", description_call_v3, additional_constraints_call_v3, nullptr, 0,
false, true},
{"canary_client_privacy", description_canary_client_privacy,
additional_constraints_canary_client_privacy, nullptr, 0, false, false},
{"client_privacy", description_client_privacy,
@ -588,6 +598,8 @@ const ExperimentMetadata g_experiment_metadata[] = {
{"work_serializer_dispatch", description_work_serializer_dispatch,
additional_constraints_work_serializer_dispatch,
required_experiments_work_serializer_dispatch, 1, true, true},
{"call_v3", description_call_v3, additional_constraints_call_v3,
required_experiments_call_v3, 3, false, false},
};
} // namespace grpc_core

@ -59,7 +59,6 @@ namespace grpc_core {
#if defined(GRPC_CFSTREAM)
#define GRPC_EXPERIMENT_IS_INCLUDED_CALL_STATUS_OVERRIDE_ON_CANCELLATION
inline bool IsCallStatusOverrideOnCancellationEnabled() { return true; }
inline bool IsCallV3Enabled() { return false; }
inline bool IsCanaryClientPrivacyEnabled() { return false; }
inline bool IsClientPrivacyEnabled() { return false; }
inline bool IsEventEngineClientEnabled() { return false; }
@ -91,11 +90,11 @@ inline bool IsUnconstrainedMaxQuotaBufferSizeEnabled() { return false; }
#define GRPC_EXPERIMENT_IS_INCLUDED_WORK_SERIALIZER_CLEARS_TIME_CACHE
inline bool IsWorkSerializerClearsTimeCacheEnabled() { return true; }
inline bool IsWorkSerializerDispatchEnabled() { return false; }
inline bool IsCallV3Enabled() { return false; }
#elif defined(GPR_WINDOWS)
#define GRPC_EXPERIMENT_IS_INCLUDED_CALL_STATUS_OVERRIDE_ON_CANCELLATION
inline bool IsCallStatusOverrideOnCancellationEnabled() { return true; }
inline bool IsCallV3Enabled() { return false; }
inline bool IsCanaryClientPrivacyEnabled() { return false; }
inline bool IsClientPrivacyEnabled() { return false; }
inline bool IsEventEngineClientEnabled() { return false; }
@ -129,11 +128,11 @@ inline bool IsUnconstrainedMaxQuotaBufferSizeEnabled() { return false; }
#define GRPC_EXPERIMENT_IS_INCLUDED_WORK_SERIALIZER_CLEARS_TIME_CACHE
inline bool IsWorkSerializerClearsTimeCacheEnabled() { return true; }
inline bool IsWorkSerializerDispatchEnabled() { return false; }
inline bool IsCallV3Enabled() { return false; }
#else
#define GRPC_EXPERIMENT_IS_INCLUDED_CALL_STATUS_OVERRIDE_ON_CANCELLATION
inline bool IsCallStatusOverrideOnCancellationEnabled() { return true; }
inline bool IsCallV3Enabled() { return false; }
inline bool IsCanaryClientPrivacyEnabled() { return false; }
inline bool IsClientPrivacyEnabled() { return false; }
inline bool IsEventEngineClientEnabled() { return false; }
@ -168,12 +167,12 @@ inline bool IsUnconstrainedMaxQuotaBufferSizeEnabled() { return false; }
inline bool IsWorkSerializerClearsTimeCacheEnabled() { return true; }
#define GRPC_EXPERIMENT_IS_INCLUDED_WORK_SERIALIZER_DISPATCH
inline bool IsWorkSerializerDispatchEnabled() { return true; }
inline bool IsCallV3Enabled() { return false; }
#endif
#else
enum ExperimentIds {
kExperimentIdCallStatusOverrideOnCancellation,
kExperimentIdCallV3,
kExperimentIdCanaryClientPrivacy,
kExperimentIdClientPrivacy,
kExperimentIdEventEngineClient,
@ -200,16 +199,13 @@ enum ExperimentIds {
kExperimentIdUnconstrainedMaxQuotaBufferSize,
kExperimentIdWorkSerializerClearsTimeCache,
kExperimentIdWorkSerializerDispatch,
kExperimentIdCallV3,
kNumExperiments
};
#define GRPC_EXPERIMENT_IS_INCLUDED_CALL_STATUS_OVERRIDE_ON_CANCELLATION
inline bool IsCallStatusOverrideOnCancellationEnabled() {
return IsExperimentEnabled(kExperimentIdCallStatusOverrideOnCancellation);
}
#define GRPC_EXPERIMENT_IS_INCLUDED_CALL_V3
inline bool IsCallV3Enabled() {
return IsExperimentEnabled(kExperimentIdCallV3);
}
#define GRPC_EXPERIMENT_IS_INCLUDED_CANARY_CLIENT_PRIVACY
inline bool IsCanaryClientPrivacyEnabled() {
return IsExperimentEnabled(kExperimentIdCanaryClientPrivacy);
@ -314,6 +310,10 @@ inline bool IsWorkSerializerClearsTimeCacheEnabled() {
inline bool IsWorkSerializerDispatchEnabled() {
return IsExperimentEnabled(kExperimentIdWorkSerializerDispatch);
}
#define GRPC_EXPERIMENT_IS_INCLUDED_CALL_V3
inline bool IsCallV3Enabled() {
return IsExperimentEnabled(kExperimentIdCallV3);
}
extern const ExperimentMetadata g_experiment_metadata[kNumExperiments];

@ -52,6 +52,8 @@
expiry: 2024/06/01
owner: ctiller@google.com
test_tags: []
requires: ["work_serializer_dispatch", "event_engine_listener", "event_engine_client"]
allow_in_fuzzing_config: false
- name: canary_client_privacy
description:
If set, canary client privacy

@ -26,6 +26,7 @@
#include "absl/base/thread_annotations.h"
#include "absl/log/check.h"
#include "absl/status/status.h"
#include "absl/strings/str_cat.h"
#include "absl/types/optional.h"
#include <grpc/support/log.h>
@ -121,6 +122,11 @@ class Waker {
return wakeable_and_arg_.ActivityDebugTag();
}
std::string DebugString() {
return absl::StrFormat("Waker{%p, %d}", wakeable_and_arg_.wakeable,
wakeable_and_arg_.wakeup_mask);
}
// This is for tests to assert that a waker is occupied or not.
bool is_unwakeable() const {
return wakeable_and_arg_.wakeable == promise_detail::unwakeable();

@ -125,6 +125,11 @@ T* MaybeGetContext() {
return promise_detail::Context<T>::get();
}
template <typename T>
void SetContext(T* p) {
promise_detail::Context<T>::set(p);
}
// Given a promise and a context, return a promise that has that context set.
template <typename T, typename F>
promise_detail::WithContext<T, F> WithContext(F f, T* context) {

@ -72,6 +72,23 @@ promise_detail::Map<Promise, Fn> Map(Promise promise, Fn fn) {
return promise_detail::Map<Promise, Fn>(std::move(promise), std::move(fn));
}
// Maps a promise to a new promise that returns a tuple of the original result
// and a bool indicating whether there was ever a Pending{} value observed from
// polling.
template <typename Promise>
auto CheckDelayed(Promise promise) {
using P = promise_detail::PromiseLike<Promise>;
return [delayed = false, promise = P(std::move(promise))]() mutable
-> Poll<std::tuple<typename P::Result, bool>> {
auto r = promise();
if (r.pending()) {
delayed = true;
return Pending{};
}
return std::make_tuple(r.value(), delayed);
};
}
// Callable that takes a tuple and returns one element
template <size_t kElem>
struct JustElem {

@ -125,6 +125,7 @@ class Observable {
Observer& operator=(const Observer&) = delete;
Observer(Observer&& other) noexcept : state_(std::move(other.state_)) {
CHECK(other.waker_.is_unwakeable());
DCHECK(waker_.is_unwakeable());
CHECK(!other.saw_pending_);
}
Observer& operator=(Observer&& other) noexcept = delete;

@ -308,8 +308,10 @@ void Party::AddParticipants(Participant** participants, size_t count) {
for (size_t i = 0; i < count; i++) {
if (grpc_trace_party_state.enabled()) {
gpr_log(GPR_INFO,
"Party %p AddParticipant: %s @ %" PRIdPTR,
&sync_, std::string(participants[i]->name()).c_str(), slots[i]);
"Party %p AddParticipant: %s @ %" PRIdPTR
" [participant=%p]",
&sync_, std::string(participants[i]->name()).c_str(), slots[i],
participants[i]);
}
participants_[slots[i]].store(participants[i], std::memory_order_release);
}

@ -379,6 +379,11 @@ class Party : public Activity, private Wakeable {
// The on_complete callback will be called with the result of the promise if
// it completes.
// A maximum of sixteen promises can be spawned onto a party.
// promise_factory called to create the promise with the party lock taken;
// after the promise is created the factory is destroyed.
// This means that pointers or references to factory members will be
// invalidated after the promise is created - so the promise should not retain
// any of these.
template <typename Factory, typename OnComplete>
void Spawn(absl::string_view name, Factory promise_factory,
OnComplete on_complete);

@ -18,6 +18,7 @@
#include <type_traits>
#include "absl/functional/any_invocable.h"
#include "absl/log/log.h"
#include "absl/status/status.h"
#include "absl/types/optional.h"

@ -130,6 +130,8 @@ class Call : public CppImplOf<Call, grpc_call>,
// Implementation of EventEngine::Closure, called when deadline expires
void Run() final;
gpr_cycle_counter start_time() const { return start_time_; }
protected:
// The maximum number of concurrent batches possible.
// Based upon the maximum number of individually queueable ops in the batch
@ -209,8 +211,6 @@ class Call : public CppImplOf<Call, grpc_call>,
void HandleCompressionAlgorithmNotAccepted(
grpc_compression_algorithm compression_algorithm) GPR_ATTRIBUTE_NOINLINE;
gpr_cycle_counter start_time() const { return start_time_; }
virtual grpc_compression_options compression_options() = 0;
private:

@ -65,7 +65,22 @@ Channel::RegisteredCall::~RegisteredCall() {}
Channel::Channel(std::string target, const ChannelArgs& channel_args)
: target_(std::move(target)),
channelz_node_(channel_args.GetObjectRef<channelz::ChannelNode>()),
compression_options_(CompressionOptionsFromChannelArgs(channel_args)) {}
compression_options_(CompressionOptionsFromChannelArgs(channel_args)),
call_size_estimator_(1024),
allocator_(channel_args.GetObject<ResourceQuota>()
->memory_quota()
->CreateMemoryOwner()) {}
Arena* Channel::CreateArena() {
const size_t initial_size = call_size_estimator_.CallSizeEstimate();
global_stats().IncrementCallInitialSize(initial_size);
return Arena::Create(initial_size, &allocator_);
}
void Channel::DestroyArena(Arena* arena) {
call_size_estimator_.UpdateCallSizeEstimate(arena->TotalUsedBytes());
arena->Destroy();
}
Channel::RegisteredCall* Channel::RegisterCall(const char* method,
const char* host) {

@ -40,8 +40,10 @@
#include "src/core/lib/gprpp/time.h"
#include "src/core/lib/iomgr/iomgr_fwd.h"
#include "src/core/lib/resource_quota/arena.h"
#include "src/core/lib/resource_quota/resource_quota.h"
#include "src/core/lib/slice/slice.h"
#include "src/core/lib/surface/channel_stack_type.h"
#include "src/core/lib/transport/call_arena_allocator.h"
#include "src/core/lib/transport/connectivity_state.h"
// Forward declaration to avoid dependency loop.
@ -52,7 +54,7 @@ namespace grpc_core {
// Forward declaration to avoid dependency loop.
class Transport;
class Channel : public RefCounted<Channel>,
class Channel : public InternallyRefCounted<Channel>,
public CppImplOf<Channel, grpc_channel> {
public:
struct RegisteredCall {
@ -66,10 +68,17 @@ class Channel : public RefCounted<Channel>,
~RegisteredCall();
};
virtual void Orphan() = 0;
virtual Arena* CreateArena() = 0;
virtual void DestroyArena(Arena* arena) = 0;
// Though internally ref counted channels expose their "Ref" method to
// create a RefCountedPtr to themselves. The OrphanablePtr owner is the
// singleton decision maker on whether the channel should be destroyed or
// not.
// TODO(ctiller): in a future change (I have it written) these will be removed
// and substituted with DualRefCounted<Channel> as a base.
RefCountedPtr<Channel> Ref() { return InternallyRefCounted<Channel>::Ref(); }
template <typename T>
RefCountedPtr<T> RefAsSubclass() {
return InternallyRefCounted<Channel>::RefAsSubclass<T>();
}
virtual bool IsLame() const = 0;
@ -125,6 +134,8 @@ class Channel : public RefCounted<Channel>,
virtual void Ping(grpc_completion_queue* cq, void* tag) = 0;
// TODO(roth): Remove these methods when LegacyChannel goes away.
Arena* CreateArena();
void DestroyArena(Arena* arena);
virtual grpc_channel_stack* channel_stack() const { return nullptr; }
virtual bool is_client() const { return true; }
virtual bool is_promising() const { return true; }
@ -137,6 +148,10 @@ class Channel : public RefCounted<Channel>,
const RefCountedPtr<channelz::ChannelNode> channelz_node_;
const grpc_compression_options compression_options_;
// TODO(ctiller): move to use CallArenaAllocator
CallSizeEstimator call_size_estimator_;
MemoryAllocator allocator_;
Mutex mu_;
// The map key needs to be owned strings rather than unowned char*'s to
// guarantee that it outlives calls on the core channel (which may outlast

@ -22,8 +22,10 @@
#include <grpc/support/port_platform.h>
#include "src/core/channelz/channelz.h"
#include "src/core/client_channel/client_channel.h"
#include "src/core/lib/channel/channel_args.h"
#include "src/core/lib/config/core_configuration.h"
#include "src/core/lib/experiments/experiments.h"
#include "src/core/lib/surface/channel.h"
#include "src/core/lib/surface/lame_client.h"
#include "src/core/lib/surface/legacy_channel.h"
@ -77,9 +79,13 @@ absl::StatusOr<OrphanablePtr<Channel>> ChannelCreate(
if (optional_transport != nullptr) {
args = args.SetObject(optional_transport);
}
// Delegate to legacy channel impl.
return LegacyChannel::Create(std::move(target), std::move(args),
channel_stack_type);
// Delegate to appropriate channel impl.
if (!IsCallV3Enabled()) {
return LegacyChannel::Create(std::move(target), std::move(args),
channel_stack_type);
}
CHECK_EQ(channel_stack_type, GRPC_CLIENT_CHANNEL);
return ClientChannel::Create(std::move(target), std::move(args));
}
} // namespace grpc_core

@ -113,10 +113,7 @@ LegacyChannel::LegacyChannel(bool is_client, bool is_promising,
: Channel(std::move(target), channel_args),
is_client_(is_client),
is_promising_(is_promising),
channel_stack_(std::move(channel_stack)),
allocator_(channel_args.GetObject<ResourceQuota>()
->memory_quota()
->CreateMemoryOwner()) {
channel_stack_(std::move(channel_stack)) {
// We need to make sure that grpc_shutdown() does not shut things down
// until after the channel is destroyed. However, the channel may not
// actually be destroyed by the time grpc_channel_destroy() returns,

@ -39,6 +39,7 @@
#include "src/core/lib/surface/channel.h"
#include "src/core/lib/surface/channel_stack_type.h"
#include "src/core/lib/transport/call_arena_allocator.h"
#include "src/core/lib/transport/transport.h"
#include "src/core/telemetry/stats.h"
namespace grpc_core {
@ -56,16 +57,6 @@ class LegacyChannel final : public Channel {
void Orphan() override;
Arena* CreateArena() override {
const size_t initial_size = call_size_estimator_.CallSizeEstimate();
global_stats().IncrementCallInitialSize(initial_size);
return Arena::Create(initial_size, &allocator_);
}
void DestroyArena(Arena* arena) override {
call_size_estimator_.UpdateCallSizeEstimate(arena->TotalUsedBytes());
arena->Destroy();
}
bool IsLame() const override;
grpc_call* CreateCall(grpc_call* parent_call, uint32_t propagation_mask,
@ -114,8 +105,6 @@ class LegacyChannel final : public Channel {
const bool is_client_;
const bool is_promising_;
RefCountedPtr<grpc_channel_stack> channel_stack_;
CallSizeEstimator call_size_estimator_{1024};
grpc_event_engine::experimental::MemoryAllocator allocator_;
};
} // namespace grpc_core

@ -31,6 +31,8 @@ namespace grpc_core {
class UnstartedCallDestination
: public DualRefCounted<UnstartedCallDestination> {
public:
using DualRefCounted::DualRefCounted;
~UnstartedCallDestination() override = default;
// Start a call. The UnstartedCallHandler will be consumed by the Destination
// and started.

@ -170,8 +170,15 @@ Poll<T> InfallibleOperationExecutor<T>::ContinueStep(void* call_data) {
template class OperationExecutor<ClientMetadataHandle>;
template class OperationExecutor<MessageHandle>;
template class InfallibleOperationExecutor<ServerMetadataHandle>;
} // namespace filters_detail
namespace {
// Call data for those calls that don't have any call data
// (we form pointers to this that aren't allowed to be nullptr)
char g_empty_call_data;
} // namespace
///////////////////////////////////////////////////////////////////////////////
// CallFilters
@ -181,7 +188,7 @@ CallFilters::CallFilters(ClientMetadataHandle client_initial_metadata)
client_initial_metadata_(std::move(client_initial_metadata)) {}
CallFilters::~CallFilters() {
if (call_data_ != nullptr) {
if (call_data_ != nullptr && call_data_ != &g_empty_call_data) {
for (const auto& destructor : stack_->data_.filter_destructor) {
destructor.call_destroy(Offset(call_data_, destructor.call_offset));
}
@ -192,8 +199,12 @@ CallFilters::~CallFilters() {
void CallFilters::SetStack(RefCountedPtr<Stack> stack) {
CHECK_EQ(call_data_, nullptr);
stack_ = std::move(stack);
call_data_ = gpr_malloc_aligned(stack_->data_.call_data_size,
stack_->data_.call_data_alignment);
if (stack_->data_.call_data_size != 0) {
call_data_ = gpr_malloc_aligned(stack_->data_.call_data_size,
stack_->data_.call_data_alignment);
} else {
call_data_ = &g_empty_call_data;
}
for (const auto& constructor : stack_->data_.filter_constructor) {
constructor.call_init(Offset(call_data_, constructor.call_offset),
constructor.channel_data);
@ -232,7 +243,8 @@ void CallFilters::PushServerTrailingMetadata(ServerMetadataHandle md) {
md->DebugString().c_str(), DebugString().c_str());
}
CHECK(md != nullptr);
if (server_trailing_metadata_ != nullptr) return;
if (cancelled_.is_set()) return;
cancelled_.Set(md->get(GrpcCallWasCancelled()).value_or(false));
server_trailing_metadata_ = std::move(md);
client_initial_metadata_state_.CloseWithError();
server_initial_metadata_state_.CloseSending();

@ -23,6 +23,7 @@
#include <grpc/support/port_platform.h>
#include "src/core/lib/gprpp/dump_args.h"
#include "src/core/lib/gprpp/ref_counted.h"
#include "src/core/lib/gprpp/ref_counted_ptr.h"
#include "src/core/lib/promise/latch.h"
@ -1944,11 +1945,7 @@ inline auto CallFilters::PullServerToClientMessage() {
}
inline auto CallFilters::PullServerTrailingMetadata() {
return Map(PullServerTrailingMetadataPromise(this),
[this](ServerMetadataHandle h) {
cancelled_.Set(h->get(GrpcCallWasCancelled()).value_or(false));
return h;
});
return PullServerTrailingMetadataPromise(this);
}
inline auto CallFilters::WasCancelled() { return cancelled_.Wait(); }

@ -21,13 +21,16 @@
#include <grpc/support/port_platform.h>
#include "src/core/lib/channel/context.h"
#include "src/core/lib/gprpp/dual_ref_counted.h"
#include "src/core/lib/promise/detail/status.h"
#include "src/core/lib/promise/if.h"
#include "src/core/lib/promise/latch.h"
#include "src/core/lib/promise/party.h"
#include "src/core/lib/promise/pipe.h"
#include "src/core/lib/promise/prioritized_race.h"
#include "src/core/lib/promise/promise.h"
#include "src/core/lib/promise/status_flag.h"
#include "src/core/lib/promise/try_seq.h"
#include "src/core/lib/transport/call_arena_allocator.h"
#include "src/core/lib/transport/call_filters.h"
#include "src/core/lib/transport/message.h"
@ -135,6 +138,23 @@ class CallSpineInterface {
});
}
// Wrap a promise so that if the call completes that promise is cancelled.
template <typename Promise>
auto UntilCallCompletes(Promise promise) {
using Result = PromiseResult<Promise>;
return PrioritizedRace(std::move(promise), Map(WasCancelled(), [](bool) {
return FailureStatusCast<Result>(Failure{});
}));
}
template <typename PromiseFactory>
void SpawnGuardedUntilCallCompletes(absl::string_view name,
PromiseFactory promise_factory) {
SpawnGuarded(name, [this, promise_factory]() mutable {
return UntilCallCompletes(promise_factory());
});
}
private:
absl::AnyInvocable<void()> on_done_{nullptr};
};
@ -272,7 +292,7 @@ class CallSpine final : public CallSpineInterface, public Party {
if (legacy_context_is_owned_) {
for (size_t i = 0; i < GRPC_CONTEXT_COUNT; i++) {
grpc_call_context_element& elem = legacy_context_[i];
if (elem.destroy != nullptr) elem.destroy(&elem);
if (elem.destroy != nullptr) elem.destroy(elem.value);
}
}
}
@ -472,6 +492,12 @@ class CallInitiator {
spine_->SpawnGuarded(name, std::move(promise_factory));
}
template <typename PromiseFactory>
void SpawnGuardedUntilCallCompletes(absl::string_view name,
PromiseFactory promise_factory) {
spine_->SpawnGuardedUntilCallCompletes(name, std::move(promise_factory));
}
template <typename PromiseFactory>
void SpawnInfallible(absl::string_view name, PromiseFactory promise_factory) {
spine_->SpawnInfallible(name, std::move(promise_factory));
@ -526,6 +552,12 @@ class CallHandler {
spine_->SpawnGuarded(name, std::move(promise_factory), whence);
}
template <typename PromiseFactory>
void SpawnGuardedUntilCallCompletes(absl::string_view name,
PromiseFactory promise_factory) {
spine_->SpawnGuardedUntilCallCompletes(name, std::move(promise_factory));
}
template <typename PromiseFactory>
void SpawnInfallible(absl::string_view name, PromiseFactory promise_factory) {
spine_->SpawnInfallible(name, std::move(promise_factory));
@ -577,6 +609,12 @@ class UnstartedCallHandler {
spine_->SpawnGuarded(name, std::move(promise_factory), whence);
}
template <typename PromiseFactory>
void SpawnGuardedUntilCallCompletes(absl::string_view name,
PromiseFactory promise_factory) {
spine_->SpawnGuardedUntilCallCompletes(name, std::move(promise_factory));
}
template <typename PromiseFactory>
void SpawnInfallible(absl::string_view name, PromiseFactory promise_factory) {
spine_->SpawnInfallible(name, std::move(promise_factory));

@ -528,6 +528,14 @@ struct WaitForReady {
static std::string DisplayValue(ValueType x);
};
// Annotation added by retry code to indicate a transparent retry.
struct IsTransparentRetry {
static absl::string_view DebugKey() { return "IsTransparentRetry"; }
static constexpr bool kRepeatable = false;
using ValueType = bool;
static std::string DisplayValue(ValueType x) { return x ? "true" : "false"; }
};
// Annotation added by a transport to note that server trailing metadata
// is a Trailers-Only response.
struct GrpcTrailersOnly {
@ -1536,7 +1544,8 @@ using grpc_metadata_batch_base = grpc_core::MetadataMap<
grpc_core::GrpcStreamNetworkState, grpc_core::PeerString,
grpc_core::GrpcStatusContext, grpc_core::GrpcStatusFromWire,
grpc_core::GrpcCallWasCancelled, grpc_core::WaitForReady,
grpc_core::GrpcTrailersOnly, grpc_core::GrpcTarPit,
grpc_core::IsTransparentRetry, grpc_core::GrpcTrailersOnly,
grpc_core::GrpcTarPit,
grpc_core::GrpcRegisteredMethod GRPC_CUSTOM_CLIENT_METADATA
GRPC_CUSTOM_SERVER_METADATA>;

@ -484,6 +484,16 @@ class LoadBalancingPolicy : public InternallyRefCounted<LoadBalancingPolicy> {
ChannelArgs channel_args_;
};
namespace promise_detail {
template <>
struct OldStyleContext<LoadBalancingPolicy::SubchannelCallTrackerInterface> {
static constexpr grpc_context_index kIndex =
GRPC_SUBCHANNEL_CALL_TRACKER_INTERFACE;
};
} // namespace promise_detail
} // namespace grpc_core
#endif // GRPC_SRC_CORE_LOAD_BALANCING_LB_POLICY_H

@ -33,6 +33,7 @@
#include "src/core/lib/channel/context.h"
#include "src/core/lib/gprpp/ref_counted_string.h"
#include "src/core/lib/iomgr/error.h"
#include "src/core/lib/promise/context.h"
#include "src/core/lib/resource_quota/arena.h"
#include "src/core/lib/slice/slice_buffer.h"
#include "src/core/lib/transport/call_final_info.h"
@ -221,6 +222,21 @@ void AddClientCallTracerToContext(grpc_call_context_element* call_context,
void AddServerCallTracerToContext(grpc_call_context_element* call_context,
ServerCallTracer* tracer);
template <>
struct ContextSubclass<ClientCallTracer::CallAttemptTracer> {
using Base = CallTracerInterface;
};
template <>
struct ContextSubclass<ServerCallTracer> {
using Base = CallTracerInterface;
};
template <>
struct ContextSubclass<ClientCallTracer> {
using Base = CallTracerAnnotationInterface;
};
} // namespace grpc_core
#endif // GRPC_SRC_CORE_TELEMETRY_CALL_TRACER_H

@ -19,6 +19,7 @@ CORE_SOURCE_FILES = [
'src/core/channelz/channelz.cc',
'src/core/channelz/channelz_registry.cc',
'src/core/client_channel/backup_poller.cc',
'src/core/client_channel/client_channel.cc',
'src/core/client_channel/client_channel_factory.cc',
'src/core/client_channel/client_channel_filter.cc',
'src/core/client_channel/client_channel_plugin.cc',
@ -26,6 +27,7 @@ CORE_SOURCE_FILES = [
'src/core/client_channel/config_selector.cc',
'src/core/client_channel/dynamic_filters.cc',
'src/core/client_channel/global_subchannel_pool.cc',
'src/core/client_channel/load_balanced_call_destination.cc',
'src/core/client_channel/local_subchannel_pool.cc',
'src/core/client_channel/retry_filter.cc',
'src/core/client_channel/retry_filter_legacy_call_data.cc',
@ -486,6 +488,7 @@ CORE_SOURCE_FILES = [
'src/core/lib/experiments/config.cc',
'src/core/lib/experiments/experiments.cc',
'src/core/lib/gprpp/crash.cc',
'src/core/lib/gprpp/dump_args.cc',
'src/core/lib/gprpp/examine_stack.cc',
'src/core/lib/gprpp/fork.cc',
'src/core/lib/gprpp/host_port.cc',

@ -16,7 +16,7 @@
Generate one transport test & associated fuzzer
"""
load("//bazel:grpc_build_system.bzl", "grpc_cc_test")
load("//bazel:grpc_build_system.bzl", "grpc_cc_library", "grpc_cc_test")
load("//test/core/test_util:grpc_fuzzer.bzl", "grpc_proto_fuzzer")
def grpc_yodel_test(name, deps):
@ -57,3 +57,12 @@ def grpc_yodel_test(name, deps):
corpus = "corpus/%s" % name,
proto = None,
)
def grpc_yodel_simple_test(name, **kwargs):
grpc_cc_library(
name = "%s_test_lib" % name,
testonly = True,
alwayslink = 1,
**kwargs
)
grpc_yodel_test(name, deps = ["%s_test_lib" % name])

@ -14,8 +14,12 @@
#include "test/core/call/yodel/yodel_test.h"
#include <memory>
#include "absl/random/random.h"
#include "src/core/lib/config/core_configuration.h"
#include "src/core/lib/iomgr/exec_ctx.h"
#include "src/core/lib/iomgr/timer_manager.h"
#include "src/core/lib/resource_quota/resource_quota.h"
@ -117,13 +121,13 @@ void SimpleTestRegistry::ContributeTests(std::vector<Test>& tests) {
class YodelTest::WatchDog {
public:
explicit WatchDog(YodelTest* test) : test_(test) {}
~WatchDog() { test_->event_engine_->Cancel(timer_); }
~WatchDog() { test_->state_->event_engine->Cancel(timer_); }
private:
YodelTest* const test_;
grpc_event_engine::experimental::EventEngine::TaskHandle const timer_{
test_->event_engine_->RunAfter(Duration::Minutes(5),
[this]() { test_->Timeout(); })};
test_->state_->event_engine->RunAfter(Duration::Minutes(5),
[this]() { test_->Timeout(); })};
};
///////////////////////////////////////////////////////////////////////////////
@ -131,36 +135,46 @@ class YodelTest::WatchDog {
YodelTest::YodelTest(const fuzzing_event_engine::Actions& actions,
absl::BitGenRef rng)
: rng_(rng),
event_engine_{
std::make_shared<grpc_event_engine::experimental::FuzzingEventEngine>(
[]() {
grpc_timer_manager_set_threading(false);
grpc_event_engine::experimental::FuzzingEventEngine::Options
options;
return options;
}(),
actions)},
call_arena_allocator_{MakeRefCounted<CallArenaAllocator>(
MakeResourceQuota("test-quota")
->memory_quota()
->CreateMemoryAllocator("test-allocator"),
1024)} {}
: rng_(rng), actions_(actions) {}
void YodelTest::RunTest() {
TestImpl();
CoreConfiguration::Reset();
InitCoreConfiguration();
state_ = std::make_unique<State>();
state_->event_engine =
std::make_shared<grpc_event_engine::experimental::FuzzingEventEngine>(
[]() {
grpc_timer_manager_set_threading(false);
grpc_event_engine::experimental::FuzzingEventEngine::Options
options;
return options;
}(),
actions_);
state_->call_arena_allocator = MakeRefCounted<CallArenaAllocator>(
ResourceQuota::Default()->memory_quota()->CreateMemoryAllocator(
"test-allocator"),
1024);
{
ExecCtx exec_ctx;
InitTest();
}
{
ExecCtx exec_ctx;
TestImpl();
}
EXPECT_EQ(pending_actions_.size(), 0)
<< "There are still pending actions: did you forget to call "
"WaitForAllPendingWork()?";
Shutdown();
event_engine_->TickUntilIdle();
event_engine_->UnsetGlobalHooks();
state_->event_engine->TickUntilIdle();
state_->event_engine->UnsetGlobalHooks();
}
void YodelTest::TickUntilTrue(absl::FunctionRef<bool()> poll) {
WatchDog watchdog(this);
while (!poll()) {
event_engine_->Tick();
ExecCtx exec_ctx;
state_->event_engine->Tick();
}
}
@ -171,7 +185,7 @@ void YodelTest::WaitForAllPendingWork() {
pending_actions_.pop();
continue;
}
event_engine_->Tick();
state_->event_engine->Tick();
}
}

@ -350,7 +350,7 @@ class YodelTest : public ::testing::Test {
yodel_detail::SequenceSpawner(
name_and_location,
yodel_detail::SpawnerForContext(std::move(context),
event_engine_.get()),
state_->event_engine.get()),
[this](yodel_detail::NameAndLocation name_and_location, int step) {
auto action = std::make_shared<yodel_detail::ActionState>(
name_and_location, step);
@ -361,9 +361,10 @@ class YodelTest : public ::testing::Test {
}
auto MakeCall(ClientMetadataHandle client_initial_metadata) {
auto* arena = call_arena_allocator_->MakeArena();
return MakeCallPair(std::move(client_initial_metadata), event_engine_.get(),
arena, call_arena_allocator_, nullptr);
auto* arena = state_->call_arena_allocator->MakeArena();
return MakeCallPair(std::move(client_initial_metadata),
state_->event_engine.get(), arena,
state_->call_arena_allocator, nullptr);
}
void WaitForAllPendingWork();
@ -384,25 +385,35 @@ class YodelTest : public ::testing::Test {
const std::shared_ptr<grpc_event_engine::experimental::FuzzingEventEngine>&
event_engine() {
return event_engine_;
return state_->event_engine;
}
private:
class WatchDog;
struct State {
grpc::testing::TestGrpcScope grpc_scope;
std::shared_ptr<grpc_event_engine::experimental::FuzzingEventEngine>
event_engine;
RefCountedPtr<CallArenaAllocator> call_arena_allocator;
};
virtual void TestImpl() = 0;
void Timeout();
void TickUntilTrue(absl::FunctionRef<bool()> poll);
// Called before the test runs, after core configuration has been reset
// and before the event engine is started.
// This is a good time to register any custom core configuration builders.
virtual void InitCoreConfiguration() {}
// Called after the event engine has been started, but before the test runs.
virtual void InitTest() {}
// Called after the test has run, but before the event engine is shut down.
virtual void Shutdown() {}
grpc::testing::TestGrpcScope grpc_scope_;
absl::BitGenRef rng_;
const std::shared_ptr<grpc_event_engine::experimental::FuzzingEventEngine>
event_engine_;
const RefCountedPtr<CallArenaAllocator> call_arena_allocator_;
fuzzing_event_engine::Actions actions_;
std::unique_ptr<State> state_;
std::queue<std::shared_ptr<yodel_detail::ActionState>> pending_actions_;
};

@ -13,14 +13,15 @@
# limitations under the License.
load("//bazel:grpc_build_system.bzl", "grpc_cc_test", "grpc_package")
load("//test/core/call/yodel:grpc_yodel_test.bzl", "grpc_yodel_simple_test")
grpc_package(name = "test/core/client_channel")
licenses(["notice"])
grpc_cc_test(
name = "client_channel_test",
srcs = ["client_channel_test.cc"],
name = "subchannel_args_test",
srcs = ["subchannel_args_test.cc"],
external_deps = ["gtest"],
language = "C++",
uses_event_engine = False,
@ -33,6 +34,36 @@ grpc_cc_test(
],
)
grpc_yodel_simple_test(
name = "client_channel",
srcs = ["client_channel_test.cc"],
external_deps = ["gtest"],
deps = [
"//:grpc_client_channel",
"//test/core/call/yodel:yodel_test",
],
)
grpc_yodel_simple_test(
name = "connected_subchannel",
srcs = ["connected_subchannel_test.cc"],
external_deps = ["gtest"],
deps = [
"//:grpc_client_channel",
"//test/core/call/yodel:yodel_test",
],
)
grpc_yodel_simple_test(
name = "load_balanced_call_destination",
srcs = ["load_balanced_call_destination_test.cc"],
external_deps = ["gtest"],
deps = [
"//:grpc_client_channel",
"//test/core/call/yodel:yodel_test",
],
)
grpc_cc_test(
name = "retry_throttle_test",
srcs = ["retry_throttle_test.cc"],

@ -1,5 +1,4 @@
//
// Copyright 2022 gRPC authors.
// Copyright 2024 gRPC authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@ -12,79 +11,272 @@
// 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 "src/core/client_channel/client_channel.h"
#include <atomic>
#include <memory>
#include "absl/types/optional.h"
#include "absl/strings/string_view.h"
#include "gtest/gtest.h"
#include <grpc/impl/channel_arg_names.h>
#include <grpc/support/port_platform.h>
#include <grpc/grpc.h>
#include "src/core/client_channel/client_channel_filter.h"
#include "src/core/client_channel/subchannel_pool_interface.h"
#include "src/core/lib/channel/channel_args.h"
#include "src/core/resolver/endpoint_addresses.h"
#include "test/core/test_util/test_config.h"
#include "src/core/lib/address_utils/parse_address.h"
#include "src/core/lib/config/core_configuration.h"
#include "test/core/call/yodel/yodel_test.h"
namespace grpc_core {
namespace testing {
namespace {
TEST(MakeSubchannelArgs, UsesChannelDefaultAuthorityByDefault) {
ChannelArgs args = ClientChannelFilter::MakeSubchannelArgs(
ChannelArgs(), ChannelArgs(), nullptr, "foo.example.com");
EXPECT_EQ(args.GetString(GRPC_ARG_DEFAULT_AUTHORITY), "foo.example.com");
}
using EventEngine = grpc_event_engine::experimental::EventEngine;
TEST(MakeSubchannelArgs, DefaultAuthorityFromChannelArgs) {
ChannelArgs args = ClientChannelFilter::MakeSubchannelArgs(
ChannelArgs().Set(GRPC_ARG_DEFAULT_AUTHORITY, "bar.example.com"),
ChannelArgs(), nullptr, "foo.example.com");
EXPECT_EQ(args.GetString(GRPC_ARG_DEFAULT_AUTHORITY), "bar.example.com");
namespace {
const absl::string_view kTestScheme = "test";
const absl::string_view kTestTarget = "/target";
const absl::string_view kTestPath = "/test_method";
std::string TestTarget() {
return absl::StrCat(kTestScheme, "://", kTestTarget);
}
} // namespace
TEST(MakeSubchannelArgs, DefaultAuthorityFromResolver) {
ChannelArgs args = ClientChannelFilter::MakeSubchannelArgs(
ChannelArgs(),
ChannelArgs().Set(GRPC_ARG_DEFAULT_AUTHORITY, "bar.example.com"), nullptr,
"foo.example.com");
EXPECT_EQ(args.GetString(GRPC_ARG_DEFAULT_AUTHORITY), "bar.example.com");
}
class ClientChannelTest : public YodelTest {
protected:
using YodelTest::YodelTest;
TEST(MakeSubchannelArgs,
DefaultAuthorityFromChannelArgsOverridesValueFromResolver) {
ChannelArgs args = ClientChannelFilter::MakeSubchannelArgs(
ChannelArgs().Set(GRPC_ARG_DEFAULT_AUTHORITY, "bar.example.com"),
ChannelArgs().Set(GRPC_ARG_DEFAULT_AUTHORITY, "baz.example.com"), nullptr,
"foo.example.com");
EXPECT_EQ(args.GetString(GRPC_ARG_DEFAULT_AUTHORITY), "bar.example.com");
}
ClientChannel& InitChannel(const ChannelArgs& args) {
auto channel = ClientChannel::Create(TestTarget(), CompleteArgs(args));
CHECK_OK(channel);
channel_ = OrphanablePtr<ClientChannel>(
DownCast<ClientChannel*>(channel->release()));
return *channel_;
}
ClientChannel& channel() { return *channel_; }
ClientMetadataHandle MakeClientInitialMetadata() {
auto client_initial_metadata = Arena::MakePooled<ClientMetadata>();
client_initial_metadata->Set(HttpPathMetadata(),
Slice::FromCopiedString(kTestPath));
return client_initial_metadata;
}
CallHandler TickUntilCallStarted() {
return TickUntil<CallHandler>([this]() -> Poll<CallHandler> {
auto handler = call_destination_->PopHandler();
if (handler.has_value()) return std::move(*handler);
return Pending();
});
}
void QueueNameResolutionResult(Resolver::Result result) {
if (resolver_ != nullptr) {
resolver_->QueueNameResolutionResult(std::move(result));
} else {
early_resolver_results_.push(std::move(result));
}
}
Resolver::Result MakeSuccessfulResolutionResult(
absl::string_view endpoint_address) {
Resolver::Result result;
grpc_resolved_address address;
CHECK(grpc_parse_uri(URI::Parse(endpoint_address).value(), &address));
result.addresses = EndpointAddressesList({EndpointAddresses{address, {}}});
return result;
}
private:
class TestConnector final : public SubchannelConnector {
public:
void Connect(const Args&, Result*, grpc_closure* notify) override {
CHECK_EQ(notify_, nullptr);
notify_ = notify;
}
void Shutdown(grpc_error_handle error) override {
if (notify_ != nullptr) ExecCtx::Run(DEBUG_LOCATION, notify_, error);
}
private:
grpc_closure* notify_ = nullptr;
};
class TestClientChannelFactory final : public ClientChannelFactory {
public:
RefCountedPtr<Subchannel> CreateSubchannel(
const grpc_resolved_address& address,
const ChannelArgs& args) override {
gpr_log(GPR_INFO, "CreateSubchannel: args=%s", args.ToString().c_str());
return Subchannel::Create(MakeOrphanable<TestConnector>(), address, args);
}
};
class TestCallDestination final : public UnstartedCallDestination {
public:
void StartCall(UnstartedCallHandler unstarted_call_handler) override {
handlers_.push(
unstarted_call_handler.V2HackToStartCallWithoutACallFilterStack());
}
absl::optional<CallHandler> PopHandler() {
if (handlers_.empty()) return absl::nullopt;
auto handler = std::move(handlers_.front());
handlers_.pop();
return handler;
}
void Orphaned() override {}
private:
std::queue<CallHandler> handlers_;
};
TEST(MakeSubchannelArgs, ArgsFromChannelTrumpPerAddressArgs) {
ChannelArgs args = ClientChannelFilter::MakeSubchannelArgs(
ChannelArgs().Set("foo", 1), ChannelArgs().Set("foo", 2), nullptr,
"foo.example.com");
EXPECT_EQ(args.GetInt("foo"), 1);
class TestCallDestinationFactory final
: public ClientChannel::CallDestinationFactory {
public:
explicit TestCallDestinationFactory(ClientChannelTest* test)
: test_(test) {}
RefCountedPtr<UnstartedCallDestination> CreateCallDestination(
ClientChannel::PickerObservable picker) override {
CHECK(!test_->picker_.has_value());
test_->picker_ = std::move(picker);
return test_->call_destination_;
}
private:
ClientChannelTest* const test_;
};
class TestResolver final : public Resolver {
public:
explicit TestResolver(
ClientChannelTest* test, ChannelArgs args,
std::unique_ptr<Resolver::ResultHandler> result_handler,
std::shared_ptr<WorkSerializer> work_serializer)
: test_(test),
args_(std::move(args)),
result_handler_(std::move(result_handler)),
work_serializer_(std::move(work_serializer)) {
CHECK(test_->resolver_ == nullptr);
test_->resolver_ = this;
}
~TestResolver() override {
CHECK_EQ(test_->resolver_, this);
test_->resolver_ = nullptr;
}
void StartLocked() override {
while (!test_->early_resolver_results_.empty()) {
QueueNameResolutionResult(
std::move(test_->early_resolver_results_.front()));
test_->early_resolver_results_.pop();
}
}
void ShutdownLocked() override {}
void QueueNameResolutionResult(Resolver::Result result) {
result.args = result.args.UnionWith(args_);
work_serializer_->Run(
[self = RefAsSubclass<TestResolver>(),
result = std::move(result)]() mutable {
self->result_handler_->ReportResult(std::move(result));
},
DEBUG_LOCATION);
}
private:
ClientChannelTest* const test_;
const ChannelArgs args_;
const std::unique_ptr<Resolver::ResultHandler> result_handler_;
const std::shared_ptr<WorkSerializer> work_serializer_;
};
class TestResolverFactory final : public ResolverFactory {
public:
explicit TestResolverFactory(ClientChannelTest* test) : test_(test) {}
OrphanablePtr<Resolver> CreateResolver(ResolverArgs args) const override {
CHECK_EQ(args.uri.scheme(), kTestScheme);
CHECK_EQ(args.uri.path(), kTestTarget);
return MakeOrphanable<TestResolver>(test_, std::move(args.args),
std::move(args.result_handler),
std::move(args.work_serializer));
}
absl::string_view scheme() const override { return "test"; }
bool IsValidUri(const URI&) const override { return true; }
private:
ClientChannelTest* const test_;
};
ChannelArgs CompleteArgs(const ChannelArgs& args) {
return args.SetObject(&call_destination_factory_)
.SetObject(&client_channel_factory_)
.SetObject(ResourceQuota::Default())
.SetObject(std::static_pointer_cast<EventEngine>(event_engine()))
// TODO(ctiller): remove once v3 supports retries?
.SetIfUnset(GRPC_ARG_ENABLE_RETRIES, 0);
}
void InitCoreConfiguration() override {
CoreConfiguration::RegisterBuilder(
[this](CoreConfiguration::Builder* builder) {
builder->resolver_registry()->RegisterResolverFactory(
std::make_unique<TestResolverFactory>(this));
});
}
void Shutdown() override {
ExecCtx exec_ctx;
channel_.reset();
picker_.reset();
}
OrphanablePtr<ClientChannel> channel_;
absl::optional<ClientChannel::PickerObservable> picker_;
TestCallDestinationFactory call_destination_factory_{this};
TestClientChannelFactory client_channel_factory_;
RefCountedPtr<TestCallDestination> call_destination_ =
MakeRefCounted<TestCallDestination>();
// Resolver results that have been reported before the resolver has been
// instantiated.
std::queue<Resolver::Result> early_resolver_results_;
TestResolver* resolver_ = nullptr;
};
#define CLIENT_CHANNEL_TEST(name) YODEL_TEST(ClientChannelTest, name)
CLIENT_CHANNEL_TEST(NoOp) { InitChannel(ChannelArgs()); }
CLIENT_CHANNEL_TEST(CreateCall) {
auto& channel = InitChannel(ChannelArgs());
auto call_initiator = channel.CreateCall(MakeClientInitialMetadata());
SpawnTestSeq(call_initiator, "cancel", [call_initiator]() mutable {
call_initiator.Cancel();
return Empty{};
});
WaitForAllPendingWork();
}
TEST(MakeSubchannelArgs, StripsOutNoSubchannelArgs) {
ChannelArgs args = ClientChannelFilter::MakeSubchannelArgs(
ChannelArgs().Set(GRPC_ARG_NO_SUBCHANNEL_PREFIX "foo", 1),
ChannelArgs().Set(GRPC_ARG_NO_SUBCHANNEL_PREFIX "bar", 1), nullptr,
"foo.example.com");
EXPECT_EQ(args.GetString(GRPC_ARG_NO_SUBCHANNEL_PREFIX "foo"), absl::nullopt);
EXPECT_EQ(args.GetString(GRPC_ARG_NO_SUBCHANNEL_PREFIX "bar"), absl::nullopt);
CLIENT_CHANNEL_TEST(StartCall) {
auto& channel = InitChannel(ChannelArgs());
auto call_initiator = channel.CreateCall(MakeClientInitialMetadata());
QueueNameResolutionResult(
MakeSuccessfulResolutionResult("ipv4:127.0.0.1:1234"));
auto call_handler = TickUntilCallStarted();
SpawnTestSeq(call_initiator, "cancel", [call_initiator]() mutable {
call_initiator.Cancel();
return Empty{};
});
WaitForAllPendingWork();
}
} // namespace
} // namespace testing
} // namespace grpc_core
// TODO(ctiller, roth): MANY more test cases
// - Resolver returns an error for the initial result, then returns a valid
// result.
// - Resolver returns a service config (various permutations).
int main(int argc, char** argv) {
::testing::InitGoogleTest(&argc, argv);
grpc::testing::TestEnvironment env(&argc, argv);
auto result = RUN_ALL_TESTS();
return result;
}
} // namespace grpc_core

@ -0,0 +1,178 @@
// Copyright 2024 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 <atomic>
#include <memory>
#include "absl/status/status.h"
#include "absl/strings/string_view.h"
#include "gtest/gtest.h"
#include <grpc/grpc.h>
#include "src/core/client_channel/client_channel.h"
#include "src/core/client_channel/local_subchannel_pool.h"
#include "src/core/lib/address_utils/parse_address.h"
#include "src/core/lib/config/core_configuration.h"
#include "test/core/call/yodel/yodel_test.h"
namespace grpc_core {
using EventEngine = grpc_event_engine::experimental::EventEngine;
namespace {
const absl::string_view kTestPath = "/test_method";
const absl::string_view kTestAddress = "ipv4:127.0.0.1:1234";
const absl::string_view kDefaultAuthority = "test-authority";
} // namespace
class ConnectedSubchannelTest : public YodelTest {
protected:
using YodelTest::YodelTest;
RefCountedPtr<ConnectedSubchannel> InitChannel(const ChannelArgs& args) {
grpc_resolved_address addr;
CHECK(grpc_parse_uri(URI::Parse(kTestAddress).value(), &addr));
auto subchannel = Subchannel::Create(MakeOrphanable<TestConnector>(this),
addr, CompleteArgs(args));
{
ExecCtx exec_ctx;
subchannel->RequestConnection();
}
return TickUntil<RefCountedPtr<ConnectedSubchannel>>(
[subchannel]() -> Poll<RefCountedPtr<ConnectedSubchannel>> {
auto connected_subchannel = subchannel->connected_subchannel();
if (connected_subchannel != nullptr) return connected_subchannel;
return Pending();
});
}
ClientMetadataHandle MakeClientInitialMetadata() {
auto client_initial_metadata = Arena::MakePooled<ClientMetadata>();
client_initial_metadata->Set(HttpPathMetadata(),
Slice::FromCopiedString(kTestPath));
return client_initial_metadata;
}
CallInitiatorAndHandler MakeCall(
ClientMetadataHandle client_initial_metadata) {
return MakeCallPair(
std::move(client_initial_metadata), event_engine().get(),
call_arena_allocator_->MakeArena(), call_arena_allocator_, nullptr);
}
CallHandler TickUntilCallStarted() {
return TickUntil<CallHandler>([this]() -> Poll<CallHandler> {
auto handler = PopHandler();
if (handler.has_value()) return std::move(*handler);
return Pending();
});
}
private:
class TestTransport final : public ClientTransport {
public:
explicit TestTransport(ConnectedSubchannelTest* test) : test_(test) {}
void Orphan() override {
state_tracker_.SetState(GRPC_CHANNEL_SHUTDOWN, absl::OkStatus(),
"transport-orphaned");
Unref();
}
FilterStackTransport* filter_stack_transport() override { return nullptr; }
ClientTransport* client_transport() override { return this; }
ServerTransport* server_transport() override { return nullptr; }
absl::string_view GetTransportName() const override { return "test"; }
void SetPollset(grpc_stream*, grpc_pollset*) override {}
void SetPollsetSet(grpc_stream*, grpc_pollset_set*) override {}
void PerformOp(grpc_transport_op* op) override {
LOG(INFO) << "PerformOp: " << grpc_transport_op_string(op);
if (op->start_connectivity_watch != nullptr) {
state_tracker_.AddWatcher(op->start_connectivity_watch_state,
std::move(op->start_connectivity_watch));
}
ExecCtx::Run(DEBUG_LOCATION, op->on_consumed, absl::OkStatus());
}
void StartCall(CallHandler call_handler) override {
test_->handlers_.push(std::move(call_handler));
}
private:
ConnectedSubchannelTest* const test_;
ConnectivityStateTracker state_tracker_{"test-transport"};
};
class TestConnector final : public SubchannelConnector {
public:
explicit TestConnector(ConnectedSubchannelTest* test) : test_(test) {}
void Connect(const Args& args, Result* result,
grpc_closure* notify) override {
result->channel_args = args.channel_args;
result->transport = MakeOrphanable<TestTransport>(test_).release();
ExecCtx::Run(DEBUG_LOCATION, notify, absl::OkStatus());
}
void Shutdown(grpc_error_handle) override {}
private:
ConnectedSubchannelTest* const test_;
};
ChannelArgs CompleteArgs(const ChannelArgs& args) {
return args.SetObject(ResourceQuota::Default())
.SetObject(std::static_pointer_cast<EventEngine>(event_engine()))
.SetObject(MakeRefCounted<LocalSubchannelPool>())
.Set(GRPC_ARG_DEFAULT_AUTHORITY, kDefaultAuthority);
}
void InitCoreConfiguration() override {}
void Shutdown() override {}
absl::optional<CallHandler> PopHandler() {
if (handlers_.empty()) return absl::nullopt;
auto handler = std::move(handlers_.front());
handlers_.pop();
return handler;
}
std::queue<CallHandler> handlers_;
RefCountedPtr<CallArenaAllocator> call_arena_allocator_ =
MakeRefCounted<CallArenaAllocator>(
ResourceQuota::Default()->memory_quota()->CreateMemoryAllocator(
"test"),
1024);
};
#define CONNECTED_SUBCHANNEL_CHANNEL_TEST(name) \
YODEL_TEST(ConnectedSubchannelTest, name)
CONNECTED_SUBCHANNEL_CHANNEL_TEST(NoOp) { InitChannel(ChannelArgs()); }
CONNECTED_SUBCHANNEL_CHANNEL_TEST(StartCall) {
auto channel = InitChannel(ChannelArgs());
auto call = MakeCall(MakeClientInitialMetadata());
SpawnTestSeq(
call.handler, "start-call", [channel, handler = call.handler]() mutable {
channel->unstarted_call_destination()->StartCall(std::move(handler));
return Empty{};
});
auto handler = TickUntilCallStarted();
WaitForAllPendingWork();
}
} // namespace grpc_core

@ -0,0 +1,202 @@
// Copyright 2024 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 "src/core/client_channel/load_balanced_call_destination.h"
#include <atomic>
#include <memory>
#include <queue>
#include "absl/strings/string_view.h"
#include "gmock/gmock.h"
#include "gtest/gtest.h"
#include <grpc/grpc.h>
#include "test/core/call/yodel/yodel_test.h"
using testing::StrictMock;
namespace grpc_core {
using EventEngine = grpc_event_engine::experimental::EventEngine;
namespace {
const absl::string_view kTestPath = "/test_method";
} // namespace
class LoadBalancedCallDestinationTest : public YodelTest {
protected:
using YodelTest::YodelTest;
ClientMetadataHandle MakeClientInitialMetadata() {
auto client_initial_metadata = Arena::MakePooled<ClientMetadata>();
client_initial_metadata->Set(HttpPathMetadata(),
Slice::FromCopiedString(kTestPath));
return client_initial_metadata;
}
CallInitiatorAndHandler MakeCall(
ClientMetadataHandle client_initial_metadata) {
return MakeCallPair(
std::move(client_initial_metadata), event_engine().get(),
call_arena_allocator_->MakeArena(), call_arena_allocator_, nullptr);
}
CallHandler TickUntilCallStarted() {
auto poll = [this]() -> Poll<CallHandler> {
auto handler = call_destination_->PopHandler();
if (handler.has_value()) return std::move(*handler);
return Pending();
};
return TickUntil(absl::FunctionRef<Poll<CallHandler>()>(poll));
}
LoadBalancedCallDestination& destination_under_test() {
return *destination_under_test_;
}
ClientChannel::PickerObservable& picker() { return picker_; }
RefCountedPtr<SubchannelInterface> subchannel() { return subchannel_; }
private:
class TestCallDestination final : public UnstartedCallDestination {
public:
void StartCall(UnstartedCallHandler unstarted_call_handler) override {
handlers_.push(
unstarted_call_handler.V2HackToStartCallWithoutACallFilterStack());
}
absl::optional<CallHandler> PopHandler() {
if (handlers_.empty()) return absl::nullopt;
auto handler = std::move(handlers_.front());
handlers_.pop();
return handler;
}
void Orphaned() override {}
private:
std::queue<CallHandler> handlers_;
};
class TestSubchannel : public SubchannelInterfaceWithCallDestination {
public:
explicit TestSubchannel(
RefCountedPtr<UnstartedCallDestination> call_destination)
: call_destination_(std::move(call_destination)) {}
void WatchConnectivityState(
std::unique_ptr<ConnectivityStateWatcherInterface>) override {
Crash("not implemented");
}
void CancelConnectivityStateWatch(
ConnectivityStateWatcherInterface*) override {
Crash("not implemented");
}
void RequestConnection() override { Crash("not implemented"); }
void ResetBackoff() override { Crash("not implemented"); }
void AddDataWatcher(std::unique_ptr<DataWatcherInterface>) override {
Crash("not implemented");
}
void CancelDataWatcher(DataWatcherInterface*) override {
Crash("not implemented");
}
RefCountedPtr<UnstartedCallDestination> call_destination() override {
return call_destination_;
}
private:
const RefCountedPtr<UnstartedCallDestination> call_destination_;
};
void InitCoreConfiguration() override {}
void Shutdown() override {
channel_.reset();
picker_ = ClientChannel::PickerObservable(nullptr);
call_destination_.reset();
destination_under_test_.reset();
call_arena_allocator_.reset();
subchannel_.reset();
}
OrphanablePtr<ClientChannel> channel_;
ClientChannel::PickerObservable picker_{nullptr};
RefCountedPtr<TestCallDestination> call_destination_ =
MakeRefCounted<TestCallDestination>();
RefCountedPtr<LoadBalancedCallDestination> destination_under_test_ =
MakeRefCounted<LoadBalancedCallDestination>(picker_);
RefCountedPtr<CallArenaAllocator> call_arena_allocator_ =
MakeRefCounted<CallArenaAllocator>(
ResourceQuota::Default()->memory_quota()->CreateMemoryAllocator(
"test"),
1024);
RefCountedPtr<TestSubchannel> subchannel_ =
MakeRefCounted<TestSubchannel>(call_destination_);
};
#define LOAD_BALANCED_CALL_DESTINATION_TEST(name) \
YODEL_TEST(LoadBalancedCallDestinationTest, name)
class MockPicker : public LoadBalancingPolicy::SubchannelPicker {
public:
MOCK_METHOD(LoadBalancingPolicy::PickResult, Pick,
(LoadBalancingPolicy::PickArgs));
};
LOAD_BALANCED_CALL_DESTINATION_TEST(NoOp) {}
LOAD_BALANCED_CALL_DESTINATION_TEST(CreateCall) {
auto call = MakeCall(MakeClientInitialMetadata());
SpawnTestSeq(
call.initiator, "initiator",
[this, handler = std::move(call.handler)]() {
destination_under_test().StartCall(handler);
return Empty{};
},
[call_initiator = call.initiator]() mutable {
call_initiator.Cancel();
return Empty{};
});
WaitForAllPendingWork();
}
LOAD_BALANCED_CALL_DESTINATION_TEST(StartCall) {
auto call = MakeCall(MakeClientInitialMetadata());
SpawnTestSeq(call.initiator, "initiator",
[this, handler = std::move(call.handler)]() {
destination_under_test().StartCall(handler);
return Empty{};
});
auto mock_picker = MakeRefCounted<StrictMock<MockPicker>>();
EXPECT_CALL(*mock_picker, Pick)
.WillOnce([this](LoadBalancingPolicy::PickArgs) {
return LoadBalancingPolicy::PickResult::Complete{subchannel()};
});
picker().Set(mock_picker);
auto handler = TickUntilCallStarted();
SpawnTestSeq(call.initiator, "cancel",
[call_initiator = call.initiator]() mutable {
call_initiator.Cancel();
return Empty{};
});
WaitForAllPendingWork();
}
// TODO(roth, ctiller): more tests
// - tests for the picker returning queue, fail, and drop results.
} // namespace grpc_core

@ -0,0 +1,90 @@
//
// Copyright 2022 gRPC authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
#include <memory>
#include "absl/types/optional.h"
#include "gtest/gtest.h"
#include <grpc/impl/channel_arg_names.h>
#include <grpc/support/port_platform.h>
#include "src/core/client_channel/subchannel.h"
#include "src/core/client_channel/subchannel_pool_interface.h"
#include "src/core/lib/channel/channel_args.h"
#include "src/core/resolver/endpoint_addresses.h"
#include "test/core/test_util/test_config.h"
namespace grpc_core {
namespace testing {
namespace {
TEST(MakeSubchannelArgs, UsesChannelDefaultAuthorityByDefault) {
ChannelArgs args = Subchannel::MakeSubchannelArgs(
ChannelArgs(), ChannelArgs(), nullptr, "foo.example.com");
EXPECT_EQ(args.GetString(GRPC_ARG_DEFAULT_AUTHORITY), "foo.example.com");
}
TEST(MakeSubchannelArgs, DefaultAuthorityFromChannelArgs) {
ChannelArgs args = Subchannel::MakeSubchannelArgs(
ChannelArgs().Set(GRPC_ARG_DEFAULT_AUTHORITY, "bar.example.com"),
ChannelArgs(), nullptr, "foo.example.com");
EXPECT_EQ(args.GetString(GRPC_ARG_DEFAULT_AUTHORITY), "bar.example.com");
}
TEST(MakeSubchannelArgs, DefaultAuthorityFromResolver) {
ChannelArgs args = Subchannel::MakeSubchannelArgs(
ChannelArgs(),
ChannelArgs().Set(GRPC_ARG_DEFAULT_AUTHORITY, "bar.example.com"), nullptr,
"foo.example.com");
EXPECT_EQ(args.GetString(GRPC_ARG_DEFAULT_AUTHORITY), "bar.example.com");
}
TEST(MakeSubchannelArgs,
DefaultAuthorityFromChannelArgsOverridesValueFromResolver) {
ChannelArgs args = Subchannel::MakeSubchannelArgs(
ChannelArgs().Set(GRPC_ARG_DEFAULT_AUTHORITY, "bar.example.com"),
ChannelArgs().Set(GRPC_ARG_DEFAULT_AUTHORITY, "baz.example.com"), nullptr,
"foo.example.com");
EXPECT_EQ(args.GetString(GRPC_ARG_DEFAULT_AUTHORITY), "bar.example.com");
}
TEST(MakeSubchannelArgs, ArgsFromChannelTrumpPerAddressArgs) {
ChannelArgs args = Subchannel::MakeSubchannelArgs(ChannelArgs().Set("foo", 1),
ChannelArgs().Set("foo", 2),
nullptr, "foo.example.com");
EXPECT_EQ(args.GetInt("foo"), 1);
}
TEST(MakeSubchannelArgs, StripsOutNoSubchannelArgs) {
ChannelArgs args = Subchannel::MakeSubchannelArgs(
ChannelArgs().Set(GRPC_ARG_NO_SUBCHANNEL_PREFIX "foo", 1),
ChannelArgs().Set(GRPC_ARG_NO_SUBCHANNEL_PREFIX "bar", 1), nullptr,
"foo.example.com");
EXPECT_EQ(args.GetString(GRPC_ARG_NO_SUBCHANNEL_PREFIX "foo"), absl::nullopt);
EXPECT_EQ(args.GetString(GRPC_ARG_NO_SUBCHANNEL_PREFIX "bar"), absl::nullopt);
}
} // namespace
} // namespace testing
} // namespace grpc_core
int main(int argc, char** argv) {
::testing::InitGoogleTest(&argc, argv);
grpc::testing::TestEnvironment env(&argc, argv);
auto result = RUN_ALL_TESTS();
return result;
}

@ -145,6 +145,7 @@ grpc_cc_test(
uses_event_engine = False,
uses_polling = False,
deps = [
"poll_matcher",
"//:promise",
"//src/core:map",
],

@ -20,12 +20,13 @@
#include "gtest/gtest.h"
#include "src/core/lib/promise/promise.h"
#include "test/core/promise/poll_matcher.h"
namespace grpc_core {
TEST(MapTest, Works) {
Promise<int> x = Map([]() { return 42; }, [](int i) { return i / 2; });
EXPECT_EQ(x(), Poll<int>(21));
EXPECT_THAT(x(), IsReady(21));
}
TEST(MapTest, JustElem) {
@ -34,6 +35,21 @@ TEST(MapTest, JustElem) {
EXPECT_EQ(JustElem<0>()(t), 1);
}
TEST(CheckDelayedTest, SeesImmediate) {
auto x = CheckDelayed([]() { return 42; });
EXPECT_THAT(x(), IsReady(std::make_tuple(42, false)));
}
TEST(CheckDelayedTest, SeesDelayed) {
auto x = CheckDelayed([n = 1]() mutable -> Poll<int> {
if (n == 0) return 42;
--n;
return Pending{};
});
EXPECT_THAT(x(), IsPending());
EXPECT_THAT(x(), IsReady(std::make_tuple(42, true)));
}
} // namespace grpc_core
int main(int argc, char** argv) {

@ -140,6 +140,16 @@ TEST(ObservableTest, MultipleActivitiesWakeUp) {
EXPECT_THAT(next2(), IsReady(2));
}
TEST(ObservableTest, NoDeadlockOnDestruction) {
StrictMock<MockActivity> activity;
Observable<int> observable(1);
activity.Activate();
{
auto next = observable.Next(1);
EXPECT_THAT(next(), IsPending());
}
}
class ThreadWakeupScheduler {
public:
template <typename ActivityType>

@ -39,7 +39,7 @@ void test_unknown_scheme_target(void) {
grpc_core::Channel::FromC(chan)->channel_stack(), 0);
ASSERT_STREQ(elem->filter->name, "lame-client");
grpc_core::ExecCtx exec_ctx;
grpc_core::Channel::FromC(chan)->Unref();
grpc_core::Channel::FromC(chan)->Orphan();
creds->Unref();
}
@ -53,7 +53,7 @@ void test_security_connector_already_in_arg(void) {
grpc_core::Channel::FromC(chan)->channel_stack(), 0);
ASSERT_STREQ(elem->filter->name, "lame-client");
grpc_core::ExecCtx exec_ctx;
grpc_core::Channel::FromC(chan)->Unref();
grpc_core::Channel::FromC(chan)->Orphan();
}
void test_null_creds(void) {
@ -62,7 +62,7 @@ void test_null_creds(void) {
grpc_core::Channel::FromC(chan)->channel_stack(), 0);
ASSERT_STREQ(elem->filter->name, "lame-client");
grpc_core::ExecCtx exec_ctx;
grpc_core::Channel::FromC(chan)->Unref();
grpc_core::Channel::FromC(chan)->Orphan();
}
TEST(SecureChannelCreateTest, MainTest) {

@ -58,12 +58,10 @@ void TransportTest::ServerCallDestination::StartCall(
}
absl::optional<CallHandler> TransportTest::ServerCallDestination::PopHandler() {
if (!handlers_.empty()) {
auto handler = std::move(handlers_.front());
handlers_.pop();
return handler;
}
return absl::nullopt;
if (handlers_.empty()) return absl::nullopt;
auto handler = std::move(handlers_.front());
handlers_.pop();
return handler;
}
} // namespace grpc_core

@ -42,7 +42,7 @@ class TransportTest : public YodelTest {
TransportTest(const TransportFixture& fixture,
const fuzzing_event_engine::Actions& actions,
absl::BitGenRef rng)
: YodelTest(actions, rng), transport_pair_(fixture(event_engine())) {}
: YodelTest(actions, rng), fixture_(std::move(fixture)) {}
void SetServerCallDestination();
CallInitiator CreateCall(ClientMetadataHandle client_initial_metadata);
@ -60,6 +60,8 @@ class TransportTest : public YodelTest {
std::queue<CallHandler> handlers_;
};
void InitTest() override { transport_pair_ = fixture_(event_engine()); }
void Shutdown() override {
transport_pair_.client.reset();
transport_pair_.server.reset();
@ -67,6 +69,7 @@ class TransportTest : public YodelTest {
RefCountedPtr<ServerCallDestination> server_call_destination_ =
MakeRefCounted<ServerCallDestination>();
const TransportFixture& fixture_;
ClientAndServerTransportPair transport_pair_;
};

@ -400,6 +400,7 @@ for dirname in [
"src/cpp/ext/otel",
"test/core/backoff",
"test/core/call/yodel",
"test/core/client_channel",
"test/core/experiments",
"test/core/uri",
"test/core/test_util",
@ -430,7 +431,10 @@ for dirname in [
"grpc_cc_library": grpc_cc_library,
"grpc_cc_test": grpc_cc_library,
"grpc_core_end2end_test": lambda **kwargs: None,
"grpc_filegroup": lambda **kwargs: None,
"grpc_transport_test": lambda **kwargs: None,
"grpc_yodel_test": lambda **kwargs: None,
"grpc_yodel_simple_test": lambda **kwargs: None,
"grpc_fuzzer": grpc_cc_library,
"grpc_fuzz_test": grpc_cc_library,
"grpc_proto_fuzzer": grpc_cc_library,

@ -1093,6 +1093,8 @@ src/core/channelz/channelz_registry.cc \
src/core/channelz/channelz_registry.h \
src/core/client_channel/backup_poller.cc \
src/core/client_channel/backup_poller.h \
src/core/client_channel/client_channel.cc \
src/core/client_channel/client_channel.h \
src/core/client_channel/client_channel_factory.cc \
src/core/client_channel/client_channel_factory.h \
src/core/client_channel/client_channel_filter.cc \
@ -1108,6 +1110,8 @@ src/core/client_channel/dynamic_filters.cc \
src/core/client_channel/dynamic_filters.h \
src/core/client_channel/global_subchannel_pool.cc \
src/core/client_channel/global_subchannel_pool.h \
src/core/client_channel/load_balanced_call_destination.cc \
src/core/client_channel/load_balanced_call_destination.h \
src/core/client_channel/local_subchannel_pool.cc \
src/core/client_channel/local_subchannel_pool.h \
src/core/client_channel/retry_filter.cc \
@ -2260,6 +2264,8 @@ src/core/lib/gprpp/debug_location.h \
src/core/lib/gprpp/directory_reader.h \
src/core/lib/gprpp/down_cast.h \
src/core/lib/gprpp/dual_ref_counted.h \
src/core/lib/gprpp/dump_args.cc \
src/core/lib/gprpp/dump_args.h \
src/core/lib/gprpp/env.h \
src/core/lib/gprpp/examine_stack.cc \
src/core/lib/gprpp/examine_stack.h \
@ -2485,6 +2491,7 @@ src/core/lib/promise/interceptor_list.h \
src/core/lib/promise/latch.h \
src/core/lib/promise/loop.h \
src/core/lib/promise/map.h \
src/core/lib/promise/observable.h \
src/core/lib/promise/party.cc \
src/core/lib/promise/party.h \
src/core/lib/promise/pipe.h \

@ -896,6 +896,8 @@ src/core/channelz/channelz_registry.h \
src/core/client_channel/README.md \
src/core/client_channel/backup_poller.cc \
src/core/client_channel/backup_poller.h \
src/core/client_channel/client_channel.cc \
src/core/client_channel/client_channel.h \
src/core/client_channel/client_channel_factory.cc \
src/core/client_channel/client_channel_factory.h \
src/core/client_channel/client_channel_filter.cc \
@ -911,6 +913,8 @@ src/core/client_channel/dynamic_filters.cc \
src/core/client_channel/dynamic_filters.h \
src/core/client_channel/global_subchannel_pool.cc \
src/core/client_channel/global_subchannel_pool.h \
src/core/client_channel/load_balanced_call_destination.cc \
src/core/client_channel/load_balanced_call_destination.h \
src/core/client_channel/local_subchannel_pool.cc \
src/core/client_channel/local_subchannel_pool.h \
src/core/client_channel/retry_filter.cc \
@ -2033,6 +2037,8 @@ src/core/lib/gprpp/debug_location.h \
src/core/lib/gprpp/directory_reader.h \
src/core/lib/gprpp/down_cast.h \
src/core/lib/gprpp/dual_ref_counted.h \
src/core/lib/gprpp/dump_args.cc \
src/core/lib/gprpp/dump_args.h \
src/core/lib/gprpp/env.h \
src/core/lib/gprpp/examine_stack.cc \
src/core/lib/gprpp/examine_stack.h \
@ -2259,6 +2265,7 @@ src/core/lib/promise/interceptor_list.h \
src/core/lib/promise/latch.h \
src/core/lib/promise/loop.h \
src/core/lib/promise/map.h \
src/core/lib/promise/observable.h \
src/core/lib/promise/party.cc \
src/core/lib/promise/party.h \
src/core/lib/promise/pipe.h \

@ -2118,9 +2118,7 @@
"benchmark": false,
"ci_platforms": [
"linux",
"mac",
"posix",
"windows"
"posix"
],
"cpu_cost": 1.0,
"exclude_configs": [],
@ -2131,9 +2129,7 @@
"name": "client_channel_test",
"platforms": [
"linux",
"mac",
"posix",
"windows"
"posix"
],
"uses_polling": false
},
@ -2445,6 +2441,26 @@
],
"uses_polling": true
},
{
"args": [],
"benchmark": false,
"ci_platforms": [
"linux",
"posix"
],
"cpu_cost": 1.0,
"exclude_configs": [],
"exclude_iomgrs": [],
"flaky": false,
"gtest": true,
"language": "c++",
"name": "connected_subchannel_test",
"platforms": [
"linux",
"posix"
],
"uses_polling": false
},
{
"args": [],
"benchmark": false,
@ -5533,6 +5549,26 @@
],
"uses_polling": true
},
{
"args": [],
"benchmark": false,
"ci_platforms": [
"linux",
"posix"
],
"cpu_cost": 1.0,
"exclude_configs": [],
"exclude_iomgrs": [],
"flaky": false,
"gtest": true,
"language": "c++",
"name": "load_balanced_call_destination_test",
"platforms": [
"linux",
"posix"
],
"uses_polling": false
},
{
"args": [],
"benchmark": false,
@ -10065,6 +10101,30 @@
],
"uses_polling": false
},
{
"args": [],
"benchmark": false,
"ci_platforms": [
"linux",
"mac",
"posix",
"windows"
],
"cpu_cost": 1.0,
"exclude_configs": [],
"exclude_iomgrs": [],
"flaky": false,
"gtest": true,
"language": "c++",
"name": "subchannel_args_test",
"platforms": [
"linux",
"mac",
"posix",
"windows"
],
"uses_polling": false
},
{
"args": [],
"benchmark": false,

Loading…
Cancel
Save